From 6139879d68a55b6ed95883a483f43ac6da6570ae Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 17 Apr 2022 18:04:00 +0800 Subject: [PATCH] KeyHandler // Swiftify: day 4. --- Source/Modules/ControllerModules/KeyHandler.h | 37 +- .../Modules/ControllerModules/KeyHandler.mm | 1204 +---------------- .../KeyHandler_BuildInput.swift | 33 + .../KeyHandler_HandleCandidate.swift | 328 +++++ ...ler.swift => KeyHandler_HandleInput.swift} | 171 ++- .../ControllerModules/KeyHandler_Misc.swift | 43 + .../ControllerModules/KeyHandler_States.swift | 380 +++++- vChewing.xcodeproj/project.pbxproj | 20 +- 8 files changed, 1009 insertions(+), 1207 deletions(-) create mode 100644 Source/Modules/ControllerModules/KeyHandler_BuildInput.swift create mode 100644 Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift rename Source/Modules/ControllerModules/{KeyHandler.swift => KeyHandler_HandleInput.swift} (60%) create mode 100644 Source/Modules/ControllerModules/KeyHandler_Misc.swift diff --git a/Source/Modules/ControllerModules/KeyHandler.h b/Source/Modules/ControllerModules/KeyHandler.h index 28214727..57b2dfa0 100644 --- a/Source/Modules/ControllerModules/KeyHandler.h +++ b/Source/Modules/ControllerModules/KeyHandler.h @@ -47,11 +47,6 @@ extern InputMode imeModeNULL; @interface KeyHandler : NSObject - (BOOL)isBuilderEmpty; -- (BOOL)handleInput:(keyParser *)input - state:(InputState *)state - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback - NS_SWIFT_NAME(handle(input:state:stateCallback:errorCallback:)); - (void)fixNodeWithValue:(NSString *)value NS_SWIFT_NAME(fixNode(value:)); - (void)clear; @@ -63,34 +58,34 @@ extern InputMode imeModeNULL; @property(weak, nonatomic) id delegate; // The following items need to be exposed to Swift: +- (void)_walk; - (NSString *)_popOverflowComposingTextAndWalk; - -- (BOOL)_handleCandidateState:(InputState *)state - input:(keyParser *)input - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback - NS_SWIFT_NAME(handleCandidate(state:input:stateCallback:errorCallback:)); +- (NSArray *)_currentReadings; - (BOOL)checkWhetherToneMarkerConfirmsPhoneticReadingBuffer; - (BOOL)chkKeyValidity:(UniChar)value; +- (BOOL)ifLangModelHasUnigramsForKey:(NSString *)reading; - (BOOL)isPhoneticReadingBufferEmpty; +- (BOOL)isPrintable:(UniChar)charCode; +- (NSArray *)getCandidatesArray; +- (NSInteger)getBuilderCursorIndex; +- (NSInteger)getBuilderLength; +- (NSString *)_currentMandarinParser; - (NSString *)getCompositionFromPhoneticReadingBuffer; - (NSString *)getSyllableCompositionFromPhoneticReadingBuffer; - (void)clearPhoneticReadingBuffer; - (void)combinePhoneticReadingBufferKey:(UniChar)charCode; -- (void)doBackSpaceToPhoneticReadingBuffer; -- (void)removeBuilderAndReset:(BOOL)shouldReset; - (void)createNewBuilder; +- (void)dealWithOverrideModelSuggestions; +- (void)deleteBuilderReadingAfterCursor; +- (void)deleteBuilderReadingInFrontOfCursor; +- (void)doBackSpaceToPhoneticReadingBuffer; +- (void)ensurePhoneticParser; +- (void)insertReadingToBuilderAtCursor:(NSString *)reading; +- (void)removeBuilderAndReset:(BOOL)shouldReset; +- (void)setBuilderCursorIndex:(NSInteger)value; - (void)setInputModesToLM:(BOOL)isCHS; - (void)syncBaseLMPrefs; -- (void)ensurePhoneticParser; -- (BOOL)ifLangModelHasUnigramsForKey:(NSString *)reading; -- (void)insertReadingToBuilderAtCursor:(NSString *)reading; -- (BOOL)isPrintable:(UniChar)charCode; -- (void)dealWithOverrideModelSuggestions; -- (NSMutableArray *)getCandidatesArray; -- (NSInteger)getBuilderCursorIndex; -- (NSInteger)getBuilderLength; @end diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm index 02ad9d5f..2c87905e 100644 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ b/Source/Modules/ControllerModules/KeyHandler.mm @@ -153,7 +153,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // NON-SWIFTIFIABLE - (void)fixNodeWithValue:(NSString *)value { - size_t cursorIndex = [self _actualCandidateCursorIndex]; + NSInteger cursorIndex = [self _actualCandidateCursorIndex]; std::string stringValue(value.UTF8String); Gramambular::NodeAnchor selectedNode = _builder->grid().fixNodeSelectedCandidate(cursorIndex, stringValue); if (!mgrPrefs.useSCPCTypingMode) @@ -185,8 +185,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; break; nextPosition += node.spanningLength; } - if (nextPosition <= _builder->length()) - _builder->setCursorIndex(nextPosition); + if (nextPosition <= [self getBuilderLength]) + [self setBuilderCursorIndex:nextPosition]; } } @@ -203,1164 +203,24 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return [mgrPrefs.mandarinParserName stringByAppendingString:@"_"]; } -// MARK: - Handling Input - -- (BOOL)handleInput:(keyParser *)input - state:(InputState *)inState - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - InputState *state = inState; - UniChar charCode = input.charCode; - vChewingEmacsKey emacsKey = input.emacsKey; - - // if the inputText is empty, it's a function key combination, we ignore it - if (!input.inputText.length) - return NO; - - // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it - BOOL isFunctionKey = - ([input isCommandHold] || [input isOptionHotKey] || [input isNumericPad]) || [input isControlHotKey]; - if (![state isKindOfClass:[InputStateNotEmpty class]] && - ![state isKindOfClass:[InputStateAssociatedPhrases class]] && isFunctionKey) - return NO; - - // Caps Lock processing: if Caps Lock is ON, temporarily disable bopomofo. - // Note: Alphanumerical mode processing. - if ([input isBackSpace] || [input isEnter] || [input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || - [input isExtraChooseCandidateKeyReverse] || [input isCursorForward] || [input isCursorBackward]) - { - // do nothing if backspace is pressed -- we ignore the key - } - else if ([input isCapsLockOn]) - { - // process all possible combination, we hope. - [self clear]; - InputStateEmpty *emptyState = [[InputStateEmpty alloc] init]; - stateCallback(emptyState); - - // When shift is pressed, don't do further processing, since it outputs capital letter anyway. - if ([input isShiftHold]) - return NO; - - // if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char - // insertions. - if (charCode < 0x80 && !isprint(charCode)) - return NO; - - // commit everything in the buffer. - InputStateCommitting *committingState = - [[InputStateCommitting alloc] initWithPoppedText:[input.inputText lowercaseString]]; - stateCallback(committingState); - stateCallback(emptyState); - - return YES; - } - - if ([input isNumericPad]) - { - if (![input isLeft] && ![input isRight] && ![input isDown] && ![input isUp] && ![input isSpace] && - isprint(charCode)) - { - [self clear]; - InputStateEmpty *emptyState = [[InputStateEmpty alloc] init]; - stateCallback(emptyState); - InputStateCommitting *committing = - [[InputStateCommitting alloc] initWithPoppedText:[input.inputText lowercaseString]]; - stateCallback(committing); - stateCallback(emptyState); - return YES; - } - } - - // MARK: Handle Candidates - if ([state isKindOfClass:[InputStateChoosingCandidate class]]) - return [self _handleCandidateState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: Handle Associated Phrases - if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) - { - BOOL result = [self _handleCandidateState:state - input:input - stateCallback:stateCallback - errorCallback:errorCallback]; - if (result) - return YES; - state = [[InputStateEmpty alloc] init]; - stateCallback(state); - } - - // MARK: Handle Marking - if ([state isKindOfClass:[InputStateMarking class]]) - { - InputStateMarking *marking = (InputStateMarking *)state; - if ([self _handleMarkingState:(InputStateMarking *)state - input:input - stateCallback:stateCallback - errorCallback:errorCallback]) - return YES; - state = [marking convertToInputting]; - stateCallback(state); - } - - bool composeReading = false; - BOOL skipBpmfHandling = [input isReservedKey] || [input isControlHold] || [input isOptionHold]; - - // MARK: Handle BPMF Keys - - // see if it's valid BPMF reading - if (!skipBpmfHandling && [self chkKeyValidity:charCode]) - { - [self combinePhoneticReadingBufferKey:charCode]; - - // if we have a tone marker, we have to insert the reading to the - // builder in other words, if we don't have a tone marker, we just - // update the composing buffer - composeReading = [self checkWhetherToneMarkerConfirmsPhoneticReadingBuffer]; - if (!composeReading) - { - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - return YES; - } - } - - // see if we have composition if Enter/Space is hit and buffer is not empty - // we use "OR" conditioning so that the tone marker key is also taken into account - composeReading |= (![self isPhoneticReadingBufferEmpty] && ([input isSpace] || [input isEnter])); - if (composeReading) - { - // combine the reading - NSString *reading = [self getSyllableCompositionFromPhoneticReadingBuffer]; - - // see if we have an unigram for this - if (![self ifLangModelHasUnigramsForKey:reading]) - { - [IME prtDebugIntel:@"B49C0979"]; - errorCallback(); - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - return YES; - } - - // and insert it into the lattice - [self insertReadingToBuilderAtCursor:reading]; - - // then walk the lattice - NSString *poppedText = [self _popOverflowComposingTextAndWalk]; - - // get user override model suggestion - [self dealWithOverrideModelSuggestions]; - - // then update the text - [self clearPhoneticReadingBuffer]; - - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - inputting.poppedText = poppedText; - stateCallback(inputting); - - if (mgrPrefs.useSCPCTypingMode) - { - InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:inputting - useVerticalMode:input.useVerticalMode]; - if (choosingCandidates.candidates.count == 1) - { - [self clear]; - NSString *text = choosingCandidates.candidates.firstObject; - InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:text]; - stateCallback(committing); - - if (!mgrPrefs.associatedPhrasesEnabled) - { - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - stateCallback(empty); - } - else - { - InputStateAssociatedPhrases *associatedPhrases = - (InputStateAssociatedPhrases *)[self buildAssociatePhraseStateWithKey:text - useVerticalMode:input.useVerticalMode]; - if (associatedPhrases) - stateCallback(associatedPhrases); - else - { - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - stateCallback(empty); - } - } - } - else - stateCallback(choosingCandidates); - } - - // and tells the client that the key is consumed - return YES; - } - - // MARK: Calling candidate window using Space or Down or PageUp / PageDn. - if ([self isPhoneticReadingBufferEmpty] && [state isKindOfClass:[InputStateNotEmpty class]] && - ([input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse] || [input isSpace] || - [input isPageDown] || [input isPageUp] || [input isTab] || - (input.useVerticalMode && ([input isVerticalModeOnlyChooseCandidateKey])))) - { - if ([input isSpace]) - { - // if the spacebar is NOT set to be a selection key - if ([input isShiftHold] || !mgrPrefs.chooseCandidateUsingSpace) - { - if ([self getBuilderCursorIndex] >= [self getBuilderLength]) - { - NSString *composingBuffer = [(InputStateNotEmpty *)state composingBuffer]; - if (composingBuffer.length) - { - InputStateCommitting *committing = - [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; - stateCallback(committing); - } - [self clear]; - InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:@" "]; - stateCallback(committing); - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - stateCallback(empty); - } - else if ([self ifLangModelHasUnigramsForKey:@" "]) - { - [self insertReadingToBuilderAtCursor:@" "]; - NSString *poppedText = [self _popOverflowComposingTextAndWalk]; - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - inputting.poppedText = poppedText; - stateCallback(inputting); - } - return YES; - } - } - InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:(InputStateNotEmpty *)state - useVerticalMode:input.useVerticalMode]; - stateCallback(choosingCandidates); - return YES; - } - - // MARK: Esc - if ([input isESC]) - return [self _handleEscWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: Cursor backward - if ([input isCursorBackward] || emacsKey == vChewingEmacsKeyBackward) - return [self _handleBackwardWithState:state - input:input - stateCallback:stateCallback - errorCallback:errorCallback]; - - // MARK: Cursor forward - if ([input isCursorForward] || emacsKey == vChewingEmacsKeyForward) - return [self _handleForwardWithState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: Home - if ([input isHome] || emacsKey == vChewingEmacsKeyHome) - return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: End - if ([input isEnd] || emacsKey == vChewingEmacsKeyEnd) - return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: Ctrl+PgLf or Shift+PgLf - if (([input isControlHold] || [input isShiftHold]) && ([input isOptionHold] && [input isLeft])) - return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: Ctrl+PgRt or Shift+PgRt - if (([input isControlHold] || [input isShiftHold]) && ([input isOptionHold] && [input isRight])) - return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: AbsorbedArrowKey - if ([input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse]) - return [self _handleAbsorbedArrowKeyWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: Backspace - if ([input isBackSpace]) - return [self _handleBackspaceWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: Delete - if ([input isDelete] || emacsKey == vChewingEmacsKeyDelete) - return [self _handleDeleteWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: Enter - if ([input isEnter]) - return ([input isControlHold] && [input isCommandHold]) - ? [self _handleCtrlCommandEnterWithState:state - stateCallback:stateCallback - errorCallback:errorCallback] - : [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - // MARK: Punctuation list - if ([input isSymbolMenuPhysicalKey] && ![input isShiftHold]) - { - if (![input isOptionHold]) - { - if ([self ifLangModelHasUnigramsForKey:@"_punctuation_list "]) - { - if ([self isPhoneticReadingBufferEmpty]) - { - [self insertReadingToBuilderAtCursor:@"_punctuation_list"]; - NSString *poppedText = [self _popOverflowComposingTextAndWalk]; - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - inputting.poppedText = poppedText; - stateCallback(inputting); - InputStateChoosingCandidate *choosingCandidate = [self _buildCandidateState:inputting - useVerticalMode:input.useVerticalMode]; - stateCallback(choosingCandidate); - } - else - { // If there is still unfinished bpmf reading, ignore the punctuation - [IME prtDebugIntel:@"17446655"]; - errorCallback(); - } - return YES; - } - } - else - { - // 得在這裡先 commit buffer,不然會導致「在摁 ESC 離開符號選單時會重複輸入上一次的組字區的內容」的不當行為。 - // 於是這裡用「模擬一次 Enter 鍵的操作」使其代為執行這個 commit buffer 的動作。 - [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - SymbolNode *root = [SymbolNode root]; - InputStateSymbolTable *symbolState = [[InputStateSymbolTable alloc] initWithNode:root - useVerticalMode:input.useVerticalMode]; - stateCallback(symbolState); - return YES; - } - } - - // MARK: Punctuation - // if nothing is matched, see if it's a punctuation key for current layout. - - NSString *punctuationNamePrefix; - - if ([input isOptionHold]) - punctuationNamePrefix = @"_alt_punctuation_"; - else if ([input isControlHold]) - punctuationNamePrefix = @"_ctrl_punctuation_"; - else if (mgrPrefs.halfWidthPunctuationEnabled) - punctuationNamePrefix = @"_half_punctuation_"; - else - punctuationNamePrefix = @"_punctuation_"; - - NSString *parser = [self _currentMandarinParser]; - NSArray *arrCustomPunctuations = - @[ punctuationNamePrefix, parser, [NSString stringWithFormat:@"%c", (char)charCode] ]; - NSString *customPunctuation = [arrCustomPunctuations componentsJoinedByString:@""]; - if ([self _handlePunctuation:customPunctuation - state:state - usingVerticalMode:input.useVerticalMode - stateCallback:stateCallback - errorCallback:errorCallback]) - return YES; - - // if nothing is matched, see if it's a punctuation key. - NSArray *arrPunctuations = @[ punctuationNamePrefix, [NSString stringWithFormat:@"%c", (char)charCode] ]; - NSString *punctuation = [arrPunctuations componentsJoinedByString:@""]; - - if ([self _handlePunctuation:punctuation - state:state - usingVerticalMode:input.useVerticalMode - stateCallback:stateCallback - errorCallback:errorCallback]) - return YES; - - // 這裡不使用小麥注音 2.2. 的組字區處理方式,而是直接由詞庫負責。 - if ([input isUpperCaseASCIILetterKey]) - { - NSString *letter = [NSString stringWithFormat:@"%@%c", @"_letter_", (char)charCode]; - if ([self _handlePunctuation:letter - state:state - usingVerticalMode:input.useVerticalMode - stateCallback:stateCallback - errorCallback:errorCallback]) - return YES; - } - - // still nothing, then we update the composing buffer (some app has strange behavior if we don't do this, "thinking" - // the key is not actually consumed) 砍掉這一段會導致「F1-F12 - // 按鍵干擾組字區」的問題。暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。 - if ([state isKindOfClass:[InputStateNotEmpty class]] || ![self isPhoneticReadingBufferEmpty]) - { - [IME prtDebugIntel:[NSString - stringWithFormat:@"Blocked data: charCode: %c, keyCode: %c", charCode, input.keyCode]]; - [IME prtDebugIntel:@"A9BFF20E"]; - errorCallback(); - stateCallback(state); - return YES; - } - - return NO; -} - -- (BOOL)_handleEscWithState:(InputState *)state - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![state isKindOfClass:[InputStateInputting class]]) - return NO; - - BOOL escToClearInputBufferEnabled = mgrPrefs.escToCleanInputBuffer; - - if (escToClearInputBufferEnabled) - { - // if the option is enabled, we clear everything including the composing - // buffer, walked nodes and the reading. - [self clear]; - InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; - stateCallback(empty); - } - else - { - // if reading is not empty, we cancel the reading; Apple's built-in - // Zhuyin (and the erstwhile Hanin) has a default option that Esc - // "cancels" the current composed character and revert it to - // Bopomofo reading, in odds with the expectation of users from - // other platforms - - if (![self isPhoneticReadingBufferEmpty]) - { - [self clearPhoneticReadingBuffer]; - if (!_builder->length()) - { - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - stateCallback(empty); - } - else - { - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - } - } - } - return YES; -} - -- (BOOL)_handleBackwardWithState:(InputState *)state - input:(keyParser *)input - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![state isKindOfClass:[InputStateInputting class]]) - return NO; - - if (![self isPhoneticReadingBufferEmpty]) - { - [IME prtDebugIntel:@"6ED95318"]; - errorCallback(); - stateCallback(state); - return YES; - } - - InputStateInputting *currentState = (InputStateInputting *)state; - - if ([input isShiftHold]) - { - // Shift + left - if (currentState.cursorIndex > 0) - { - NSInteger previousPosition = - [currentState.composingBuffer previousUtf16PositionFor:currentState.cursorIndex]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer - cursorIndex:currentState.cursorIndex - markerIndex:previousPosition - readings:[self _currentReadings]]; - marking.tooltipForInputting = currentState.tooltip; - stateCallback(marking); - } - else - { - [IME prtDebugIntel:@"D326DEA3"]; - errorCallback(); - stateCallback(state); - } - } - else - { - if (_builder->cursorIndex() > 0) - { - _builder->setCursorIndex(_builder->cursorIndex() - 1); - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - } - else - { - [IME prtDebugIntel:@"7045E6F3"]; - errorCallback(); - stateCallback(state); - } - } - return YES; -} - -- (BOOL)_handleForwardWithState:(InputState *)state - input:(keyParser *)input - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![state isKindOfClass:[InputStateInputting class]]) - return NO; - - if (![self isPhoneticReadingBufferEmpty]) - { - [IME prtDebugIntel:@"B3BA5257"]; - errorCallback(); - stateCallback(state); - return YES; - } - - InputStateInputting *currentState = (InputStateInputting *)state; - - if ([input isShiftHold]) - { - // Shift + Right - if (currentState.cursorIndex < currentState.composingBuffer.length) - { - NSInteger nextPosition = [currentState.composingBuffer nextUtf16PositionFor:currentState.cursorIndex]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer - cursorIndex:currentState.cursorIndex - markerIndex:nextPosition - readings:[self _currentReadings]]; - marking.tooltipForInputting = currentState.tooltip; - stateCallback(marking); - } - else - { - [IME prtDebugIntel:@"BB7F6DB9"]; - errorCallback(); - stateCallback(state); - } - } - else - { - if (_builder->cursorIndex() < _builder->length()) - { - _builder->setCursorIndex(_builder->cursorIndex() + 1); - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - } - else - { - [IME prtDebugIntel:@"A96AAD58"]; - errorCallback(); - stateCallback(state); - } - } - - return YES; -} - -- (BOOL)_handleHomeWithState:(InputState *)state - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![state isKindOfClass:[InputStateInputting class]]) - return NO; - - if (![self isPhoneticReadingBufferEmpty]) - { - [IME prtDebugIntel:@"ABC44080"]; - errorCallback(); - stateCallback(state); - return YES; - } - - if (_builder->cursorIndex()) - { - _builder->setCursorIndex(0); - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - } - else - { - [IME prtDebugIntel:@"66D97F90"]; - errorCallback(); - stateCallback(state); - } - - return YES; -} - -- (BOOL)_handleEndWithState:(InputState *)state - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![state isKindOfClass:[InputStateInputting class]]) - return NO; - - if (![self isPhoneticReadingBufferEmpty]) - { - [IME prtDebugIntel:@"9B69908D"]; - errorCallback(); - stateCallback(state); - return YES; - } - - if (_builder->cursorIndex() != _builder->length()) - { - _builder->setCursorIndex(_builder->length()); - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - } - else - { - [IME prtDebugIntel:@"9B69908E"]; - errorCallback(); - stateCallback(state); - } - - return YES; -} - -- (BOOL)_handleAbsorbedArrowKeyWithState:(InputState *)state - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![state isKindOfClass:[InputStateInputting class]]) - return NO; - - if (![self isPhoneticReadingBufferEmpty]) - { - [IME prtDebugIntel:@"9B6F908D"]; - errorCallback(); - } - stateCallback(state); - return YES; -} - -- (BOOL)_handleBackspaceWithState:(InputState *)state - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![state isKindOfClass:[InputStateInputting class]]) - return NO; - - if ([self isPhoneticReadingBufferEmpty]) - { - if (_builder->cursorIndex()) - { - _builder->deleteReadingBeforeCursor(); - [self _walk]; - } - else - { - [IME prtDebugIntel:@"9D69908D"]; - errorCallback(); - stateCallback(state); - return YES; - } - } - else - [self doBackSpaceToPhoneticReadingBuffer]; - - if ([self isPhoneticReadingBufferEmpty] && !_builder->length()) - { - InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; - stateCallback(empty); - } - else - { - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - } - return YES; -} - -- (BOOL)_handleDeleteWithState:(InputState *)state - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![state isKindOfClass:[InputStateInputting class]]) - return NO; - - if ([self isPhoneticReadingBufferEmpty]) - { - if (_builder->cursorIndex() != _builder->length()) - { - _builder->deleteReadingAfterCursor(); - [self _walk]; - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - if (!inputting.composingBuffer.length) - { - InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; - stateCallback(empty); - } - else - { - stateCallback(inputting); - } - } - else - { - [IME prtDebugIntel:@"9B69938D"]; - errorCallback(); - stateCallback(state); - } - } - else - { - [IME prtDebugIntel:@"9C69908D"]; - errorCallback(); - stateCallback(state); - } - - return YES; -} - -- (BOOL)_handleCtrlCommandEnterWithState:(InputState *)state - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![state isKindOfClass:[InputStateInputting class]]) - return NO; - - NSArray *readings = [self _currentReadings]; - NSString *composingBuffer = (IME.areWeUsingOurOwnPhraseEditor) ? [readings componentsJoinedByString:@"-"] - : [readings componentsJoinedByString:@" "]; - - [self clear]; - - InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; - stateCallback(committing); - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - stateCallback(empty); - return YES; -} - -- (BOOL)_handleEnterWithState:(InputState *)state - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![state isKindOfClass:[InputStateInputting class]]) - return NO; - - [self clear]; - - InputStateInputting *current = (InputStateInputting *)state; - NSString *composingBuffer = current.composingBuffer; - InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; - stateCallback(committing); - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - stateCallback(empty); - return YES; -} - -- (BOOL)_handlePunctuation:(NSString *)customPunctuation - state:(InputState *)state - usingVerticalMode:(BOOL)useVerticalMode - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (![self ifLangModelHasUnigramsForKey:customPunctuation]) - return NO; - - NSString *poppedText; - if ([self isPhoneticReadingBufferEmpty]) - { - [self insertReadingToBuilderAtCursor:customPunctuation]; - poppedText = [self _popOverflowComposingTextAndWalk]; - } - else - { // If there is still unfinished bpmf reading, ignore the punctuation - [IME prtDebugIntel:@"A9B69908D"]; - errorCallback(); - stateCallback(state); - return YES; - } - - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - inputting.poppedText = poppedText; - stateCallback(inputting); - - if (mgrPrefs.useSCPCTypingMode && [self isPhoneticReadingBufferEmpty]) - { - InputStateChoosingCandidate *candidateState = [self _buildCandidateState:inputting - useVerticalMode:useVerticalMode]; - - if ([candidateState.candidates count] == 1) - { - [self clear]; - InputStateCommitting *committing = - [[InputStateCommitting alloc] initWithPoppedText:candidateState.candidates.firstObject]; - stateCallback(committing); - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - stateCallback(empty); - } - else - stateCallback(candidateState); - } - return YES; -} - -- (BOOL)_handleCandidateState:(InputState *)state - input:(keyParser *)input - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback; -{ - NSString *inputText = input.inputText; - UniChar charCode = input.charCode; - ctlCandidate *ctlCandidateCurrent = [self.delegate ctlCandidateForKeyHandler:self]; - - BOOL cancelCandidateKey = [input isBackSpace] || [input isESC] || [input isDelete] || - (([input isCursorBackward] || [input isCursorForward]) && [input isShiftHold]); - - if (cancelCandidateKey) - { - if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) - { - [self clear]; - InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; - stateCallback(empty); - } - else if (mgrPrefs.useSCPCTypingMode) - { - [self clear]; - InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; - stateCallback(empty); - } - else if ([self isBuilderEmpty]) - { - // 如果此時發現當前組字緩衝區為真空的情況的話,就將當前的組字緩衝區析構處理、強制重設輸入狀態。 - // 不然的話,一個本不該出現的真空組字緩衝區會使前後方向鍵與 BackSpace 鍵失靈。 - [self clear]; - InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; - stateCallback(empty); - } - else - { - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - } - return YES; - } - - if ([input isEnter]) - { - if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) - { - [self clear]; - InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; - stateCallback(empty); - return YES; - } - [self.delegate keyHandler:self - didSelectCandidateAtIndex:ctlCandidateCurrent.selectedCandidateIndex - ctlCandidate:ctlCandidateCurrent]; - return YES; - } - - if ([input isTab]) - { - BOOL updated = - mgrPrefs.specifyShiftTabKeyBehavior - ? ([input isShiftHold] ? [ctlCandidateCurrent showPreviousPage] : [ctlCandidateCurrent showNextPage]) - : ([input isShiftHold] ? [ctlCandidateCurrent highlightPreviousCandidate] - : [ctlCandidateCurrent highlightNextCandidate]); - if (!updated) - { - [IME prtDebugIntel:@"9B691919"]; - errorCallback(); - } - return YES; - } - - if ([input isSpace]) - { - BOOL updated = mgrPrefs.specifyShiftSpaceKeyBehavior - ? ([input isShiftHold] ? [ctlCandidateCurrent highlightNextCandidate] - : [ctlCandidateCurrent showNextPage]) - : ([input isShiftHold] ? [ctlCandidateCurrent showNextPage] - : [ctlCandidateCurrent highlightNextCandidate]); - if (!updated) - { - [IME prtDebugIntel:@"A11C781F"]; - errorCallback(); - } - return YES; - } - - if ([input isPageDown] || input.emacsKey == vChewingEmacsKeyNextPage) - { - BOOL updated = [ctlCandidateCurrent showNextPage]; - if (!updated) - { - [IME prtDebugIntel:@"9B691919"]; - errorCallback(); - } - return YES; - } - - if ([input isPageUp]) - { - BOOL updated = [ctlCandidateCurrent showPreviousPage]; - if (!updated) - { - [IME prtDebugIntel:@"9569955D"]; - errorCallback(); - } - return YES; - } - - if ([input isLeft]) - { - if ([ctlCandidateCurrent isKindOfClass:[ctlCandidateHorizontal class]]) - { - BOOL updated = [ctlCandidateCurrent highlightPreviousCandidate]; - if (!updated) - { - [IME prtDebugIntel:@"1145148D"]; - errorCallback(); - } - } - else - { - BOOL updated = [ctlCandidateCurrent showPreviousPage]; - if (!updated) - { - [IME prtDebugIntel:@"1919810D"]; - errorCallback(); - } - } - return YES; - } - - if (input.emacsKey == vChewingEmacsKeyBackward) - { - BOOL updated = [ctlCandidateCurrent highlightPreviousCandidate]; - if (!updated) - { - [IME prtDebugIntel:@"9B89308D"]; - errorCallback(); - } - return YES; - } - - if ([input isRight]) - { - if ([ctlCandidateCurrent isKindOfClass:[ctlCandidateHorizontal class]]) - { - BOOL updated = [ctlCandidateCurrent highlightNextCandidate]; - if (!updated) - { - [IME prtDebugIntel:@"9B65138D"]; - errorCallback(); - } - } - else - { - BOOL updated = [ctlCandidateCurrent showNextPage]; - if (!updated) - { - [IME prtDebugIntel:@"9244908D"]; - errorCallback(); - } - } - return YES; - } - - if (input.emacsKey == vChewingEmacsKeyForward) - { - BOOL updated = [ctlCandidateCurrent highlightNextCandidate]; - if (!updated) - { - [IME prtDebugIntel:@"9B2428D"]; - errorCallback(); - } - return YES; - } - - if ([input isUp]) - { - if ([ctlCandidateCurrent isKindOfClass:[ctlCandidateHorizontal class]]) - { - BOOL updated = [ctlCandidateCurrent showPreviousPage]; - if (!updated) - { - [IME prtDebugIntel:@"9B614524"]; - errorCallback(); - } - } - else - { - BOOL updated = [ctlCandidateCurrent highlightPreviousCandidate]; - if (!updated) - { - [IME prtDebugIntel:@"ASD9908D"]; - errorCallback(); - } - } - return YES; - } - - if ([input isDown]) - { - if ([ctlCandidateCurrent isKindOfClass:[ctlCandidateHorizontal class]]) - { - BOOL updated = [ctlCandidateCurrent showNextPage]; - if (!updated) - { - [IME prtDebugIntel:@"92B990DD"]; - errorCallback(); - } - } - else - { - BOOL updated = [ctlCandidateCurrent highlightNextCandidate]; - if (!updated) - { - [IME prtDebugIntel:@"6B99908D"]; - errorCallback(); - } - } - return YES; - } - - if ([input isHome] || input.emacsKey == vChewingEmacsKeyHome) - { - if (ctlCandidateCurrent.selectedCandidateIndex == 0) - { - [IME prtDebugIntel:@"9B6EDE8D"]; - errorCallback(); - } - else - ctlCandidateCurrent.selectedCandidateIndex = 0; - - return YES; - } - - NSArray *candidates; - - if ([state isKindOfClass:[InputStateChoosingCandidate class]]) - candidates = [(InputStateChoosingCandidate *)state candidates]; - else if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) - candidates = [(InputStateAssociatedPhrases *)state candidates]; - - if (!candidates) - return NO; - - if (([input isEnd] || input.emacsKey == vChewingEmacsKeyEnd) && candidates.count > 0) - { - if (ctlCandidateCurrent.selectedCandidateIndex == candidates.count - 1) - { - [IME prtDebugIntel:@"9B69AAAD"]; - errorCallback(); - } - else - ctlCandidateCurrent.selectedCandidateIndex = candidates.count - 1; - - return YES; - } - - if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) - { - if (![input isShiftHold]) - return NO; - } - - NSInteger index = NSNotFound; - NSString *match; - if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) - match = input.inputTextIgnoringModifiers; - else - match = inputText; - - for (NSUInteger j = 0, c = [ctlCandidateCurrent.keyLabels count]; j < c; j++) - { - VTCandidateKeyLabel *label = ctlCandidateCurrent.keyLabels[j]; - if ([match compare:label.key options:NSCaseInsensitiveSearch] == NSOrderedSame) - { - index = j; - break; - } - } - - if (index != NSNotFound) - { - NSUInteger candidateIndex = [ctlCandidateCurrent candidateIndexAtKeyLabelIndex:index]; - if (candidateIndex != NSUIntegerMax) - { - [self.delegate keyHandler:self didSelectCandidateAtIndex:candidateIndex ctlCandidate:ctlCandidateCurrent]; - return YES; - } - } - - if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) - return NO; - - if (mgrPrefs.useSCPCTypingMode) - { - NSString *punctuationNamePrefix; - - if ([input isOptionHold]) - punctuationNamePrefix = @"_alt_punctuation_"; - else if ([input isControlHold]) - punctuationNamePrefix = @"_ctrl_punctuation_"; - else if (mgrPrefs.halfWidthPunctuationEnabled) - punctuationNamePrefix = @"_half_punctuation_"; - else - punctuationNamePrefix = @"_punctuation_"; - - NSString *parser = [self _currentMandarinParser]; - - NSArray *arrCustomPunctuations = - @[ punctuationNamePrefix, parser, [NSString stringWithFormat:@"%c", (char)charCode] ]; - NSString *customPunctuation = [arrCustomPunctuations componentsJoinedByString:@""]; - - NSArray *arrPunctuations = @[ punctuationNamePrefix, [NSString stringWithFormat:@"%c", (char)charCode] ]; - NSString *punctuation = [arrPunctuations componentsJoinedByString:@""]; - - BOOL shouldAutoSelectCandidate = [self chkKeyValidity:charCode] || - [self ifLangModelHasUnigramsForKey:customPunctuation] || - [self ifLangModelHasUnigramsForKey:punctuation]; - - if (!shouldAutoSelectCandidate && [input isUpperCaseASCIILetterKey]) - { - NSString *letter = [NSString stringWithFormat:@"%@%c", @"_letter_", (char)charCode]; - if ([self ifLangModelHasUnigramsForKey:letter]) - shouldAutoSelectCandidate = YES; - } - - if (shouldAutoSelectCandidate) - { - NSUInteger candidateIndex = [ctlCandidateCurrent candidateIndexAtKeyLabelIndex:0]; - if (candidateIndex != NSUIntegerMax) - { - [self.delegate keyHandler:self - didSelectCandidateAtIndex:candidateIndex - ctlCandidate:ctlCandidateCurrent]; - [self clear]; - InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; - stateCallback(empty); - [self handleInput:input state:empty stateCallback:stateCallback errorCallback:errorCallback]; - } - return YES; - } - } - - [IME prtDebugIntel:@"172A0F81"]; - errorCallback(); - return YES; -} - #pragma mark - States Building +// NON-SWIFTIFIABLE - (InputStateInputting *)buildInputtingState { // "updating the composing buffer" means to request the client to "refresh" the text input buffer // with our "composing text" NSMutableString *composingBuffer = [[NSMutableString alloc] init]; NSInteger composedStringCursorIndex = 0; - - size_t readingCursorIndex = 0; - size_t builderCursorIndex = _builder->cursorIndex(); - NSString *tooltip = @""; // we must do some Unicode codepoint counting to find the actual cursor location for the client // i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars // locations + + size_t readingCursorIndex = 0; + size_t builderCursorIndex = [self getBuilderCursorIndex]; + for (std::vector::iterator wi = _walkedNodes.begin(), we = _walkedNodes.end(); wi != we; ++wi) { @@ -1370,7 +230,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSString *valueString = [NSString stringWithUTF8String:nodeStr.c_str()]; [composingBuffer appendString:valueString]; - NSArray *splited = [valueString split]; + NSArray *splited = [valueString split]; NSInteger codepointCount = splited.count; // this re-aligns the cursor index in the composed string @@ -1453,6 +313,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return newState; } +// NON-SWIFTIFIABLE - (void)_walk { // retrieve the most likely trellis, i.e. a Maximum Likelihood Estimation @@ -1479,6 +340,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; #endif } +// NON-SWIFTIFIABLE - (NSString *)_popOverflowComposingTextAndWalk { // in an ideal world, we can as well let the user type forever, @@ -1507,20 +369,9 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } // NON-SWIFTIFIABLE -- (size_t)_actualCandidateCursorIndex +- (NSArray *)_currentReadings { - size_t cursorIndex = _builder->cursorIndex(); - // MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase - if ((mgrPrefs.selectPhraseAfterCursorAsCandidate && (cursorIndex < _builder->length())) || !cursorIndex) - ++cursorIndex; - - return cursorIndex; -} - -// NON-SWIFTIFIABLE -- (NSArray *)_currentReadings -{ - NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; + NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; std::vector v = _builder->readings(); for (std::vector::iterator it_i = v.begin(); it_i != v.end(); ++it_i) [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; @@ -1683,14 +534,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; { // 這一整段都太 C++ 且只出現一次,就整個端過來了。 // 拆開封裝的話,只會把問題搞得更麻煩而已。 - std::string overrideValue = - (mgrPrefs.useSCPCTypingMode) - ? "" - : _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + std::string overrideValue = (mgrPrefs.useSCPCTypingMode) + ? "" + : _userOverrideModel->suggest(_walkedNodes, [self getBuilderCursorIndex], + [[NSDate date] timeIntervalSince1970]); if (!overrideValue.empty()) { - size_t cursorIndex = [self _actualCandidateCursorIndex]; + NSInteger cursorIndex = [self _actualCandidateCursorIndex]; std::vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); double highestScore = FindHighestScore(nodes, kEpsilon); _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, @@ -1698,6 +549,11 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } } +- (void)setBuilderCursorIndex:(NSInteger)value +{ + _builder->setCursorIndex(value); +} + - (NSInteger)getBuilderCursorIndex { return _builder->cursorIndex(); @@ -1708,11 +564,21 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return _builder->length(); } -- (NSMutableArray *)getCandidatesArray +- (void)deleteBuilderReadingInFrontOfCursor +{ + _builder->deleteReadingBeforeCursor(); +} + +- (void)deleteBuilderReadingAfterCursor +{ + _builder->deleteReadingAfterCursor(); +} + +- (NSArray *)getCandidatesArray { NSMutableArray *candidatesArray = [[NSMutableArray alloc] init]; - size_t cursorIndex = [self _actualCandidateCursorIndex]; + NSInteger cursorIndex = [self _actualCandidateCursorIndex]; std::vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); // sort the nodes, so that longer nodes (representing longer phrases) are placed at the top of the candidate list diff --git a/Source/Modules/ControllerModules/KeyHandler_BuildInput.swift b/Source/Modules/ControllerModules/KeyHandler_BuildInput.swift new file mode 100644 index 00000000..2b60b073 --- /dev/null +++ b/Source/Modules/ControllerModules/KeyHandler_BuildInput.swift @@ -0,0 +1,33 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Refactored from the ObjCpp-version of this class by: +// (c) 2011 and onwards The OpenVanilla Project (MIT 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 Cocoa + +// MARK: - § Build Input State. + +@objc extension KeyHandler { + +} diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift new file mode 100644 index 00000000..2a121cbe --- /dev/null +++ b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift @@ -0,0 +1,328 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Refactored from the ObjCpp-version of this class by: +// (c) 2011 and onwards The OpenVanilla Project (MIT 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 Cocoa + +// MARK: - § Handle Candidate State. +@objc extension KeyHandler { + func _handleCandidateState( + _ state: InputState, + input: keyParser, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + let inputText = input.inputText + let charCode: UniChar = input.charCode + let ctlCandidateCurrent = delegate!.ctlCandidate(for: self) as! ctlCandidate + + let cancelCandidateKey = + input.isBackSpace || input.isESC || input.isDelete + || ((input.isCursorBackward || input.isCursorForward) && input.isShiftHold) + + if cancelCandidateKey { + if state is InputState.AssociatedPhrases { + clear() + let empty = InputState.EmptyIgnoringPreviousState() + stateCallback(empty) + } else if mgrPrefs.useSCPCTypingMode { + clear() + let empty = InputState.EmptyIgnoringPreviousState() + stateCallback(empty) + } else if isBuilderEmpty() { + // 如果此時發現當前組字緩衝區為真空的情況的話, + // 就將當前的組字緩衝區析構處理、強制重設輸入狀態。 + // 否則,一個本不該出現的真空組字緩衝區會使前後方向鍵與 BackSpace 鍵失靈。 + clear() + let empty = InputState.EmptyIgnoringPreviousState() + stateCallback(empty) + } else { + let inputting = buildInputtingState() as! InputState.Inputting + stateCallback(inputting) + } + return true + } + + if input.isEnter { + if state is InputState.AssociatedPhrases { + clear() + let empty = InputState.EmptyIgnoringPreviousState() + stateCallback(empty) + return true + } + delegate!.keyHandler( + self, + didSelectCandidateAt: Int(ctlCandidateCurrent.selectedCandidateIndex), + ctlCandidate: ctlCandidateCurrent) + return true + } + + if input.isTab { + let updated: Bool = + mgrPrefs.specifyShiftTabKeyBehavior + ? (input.isShiftHold ? ctlCandidateCurrent.showPreviousPage() : ctlCandidateCurrent.showNextPage()) + : (input.isShiftHold + ? ctlCandidateCurrent.highlightPreviousCandidate() + : ctlCandidateCurrent.highlightNextCandidate()) + if !updated { + IME.prtDebugIntel("9B691919") + errorCallback() + } + return true + } + + if input.isSpace { + let updated: Bool = + mgrPrefs.specifyShiftSpaceKeyBehavior + ? (input.isShiftHold + ? ctlCandidateCurrent.highlightNextCandidate() + : ctlCandidateCurrent.showNextPage()) + : (input.isShiftHold + ? ctlCandidateCurrent.showNextPage() + : ctlCandidateCurrent.highlightNextCandidate()) + if !updated { + IME.prtDebugIntel("A11C781F") + errorCallback() + } + return true + } + + if input.isPageDown || input.emacsKey == vChewingEmacsKey.nextPage { + let updated: Bool = ctlCandidateCurrent.showNextPage() + if !updated { + IME.prtDebugIntel("9B691919") + errorCallback() + } + return true + } + + if input.isPageUp { + let updated: Bool = ctlCandidateCurrent.showPreviousPage() + if !updated { + IME.prtDebugIntel("9569955D") + errorCallback() + } + return true + } + + if input.isLeft { + if ctlCandidateCurrent is ctlCandidateHorizontal { + let updated: Bool = ctlCandidateCurrent.highlightPreviousCandidate() + if !updated { + IME.prtDebugIntel("1145148D") + errorCallback() + } + } else { + let updated: Bool = ctlCandidateCurrent.showPreviousPage() + if !updated { + IME.prtDebugIntel("1919810D") + errorCallback() + } + } + return true + } + + if input.emacsKey == vChewingEmacsKey.backward { + let updated: Bool = ctlCandidateCurrent.highlightPreviousCandidate() + if !updated { + IME.prtDebugIntel("9B89308D") + errorCallback() + } + return true + } + + if input.isRight { + if ctlCandidateCurrent is ctlCandidateHorizontal { + let updated: Bool = ctlCandidateCurrent.highlightNextCandidate() + if !updated { + IME.prtDebugIntel("9B65138D") + errorCallback() + } + } else { + let updated: Bool = ctlCandidateCurrent.showNextPage() + if !updated { + IME.prtDebugIntel("9244908D") + errorCallback() + } + } + return true + } + + if input.emacsKey == vChewingEmacsKey.forward { + let updated: Bool = ctlCandidateCurrent.highlightNextCandidate() + if !updated { + IME.prtDebugIntel("9B2428D") + errorCallback() + } + return true + } + + if input.isUp { + if ctlCandidateCurrent is ctlCandidateHorizontal { + let updated: Bool = ctlCandidateCurrent.showPreviousPage() + if !updated { + IME.prtDebugIntel("9B614524") + errorCallback() + } + } else { + let updated: Bool = ctlCandidateCurrent.highlightPreviousCandidate() + if !updated { + IME.prtDebugIntel("ASD9908D") + errorCallback() + } + } + return true + } + + if input.isDown { + if ctlCandidateCurrent is ctlCandidateHorizontal { + let updated: Bool = ctlCandidateCurrent.showNextPage() + if !updated { + IME.prtDebugIntel("92B990DD") + errorCallback() + } + } else { + let updated: Bool = ctlCandidateCurrent.highlightNextCandidate() + if !updated { + IME.prtDebugIntel("6B99908D") + errorCallback() + } + } + return true + } + + if input.isHome || input.emacsKey == vChewingEmacsKey.home { + if ctlCandidateCurrent.selectedCandidateIndex == 0 { + IME.prtDebugIntel("9B6EDE8D") + errorCallback() + } else { + ctlCandidateCurrent.selectedCandidateIndex = 0 + } + + return true + } + + var candidates: [String]! + + if state is InputState.ChoosingCandidate { + candidates = (state as! InputState.ChoosingCandidate).candidates + } else if state is InputState.AssociatedPhrases { + candidates = (state as! InputState.AssociatedPhrases).candidates + } + + if candidates.isEmpty { return false } + + if (input.isEnd || input.emacsKey == vChewingEmacsKey.end) && candidates.count > 0 { + if ctlCandidateCurrent.selectedCandidateIndex == UInt(candidates.count - 1) { + IME.prtDebugIntel("9B69AAAD") + errorCallback() + } else { + ctlCandidateCurrent.selectedCandidateIndex = UInt(candidates.count - 1) + } + + return true + } + + if state is InputState.AssociatedPhrases { + if !input.isShiftHold { return false } + } + + var index: Int = NSNotFound + var match: String! + if state is InputState.AssociatedPhrases { match = input.inputTextIgnoringModifiers } else { match = inputText } + + var j = 0 + while j < ctlCandidateCurrent.keyLabels.count { + let label: CandidateKeyLabel = ctlCandidateCurrent.keyLabels[j] + if match.compare(label.key, options: .caseInsensitive, range: nil, locale: .current) == .orderedSame { + index = j + break + } + j += 1 + } + + if index != NSNotFound { + let candidateIndex: UInt = ctlCandidateCurrent.candidateIndexAtKeyLabelIndex(UInt(index)) + if candidateIndex != UInt.max { + delegate!.keyHandler(self, didSelectCandidateAt: Int(candidateIndex), ctlCandidate: ctlCandidateCurrent) + return true + } + } + + if state is InputState.AssociatedPhrases { return false } + + if mgrPrefs.useSCPCTypingMode { + var punctuationNamePrefix = "" + + if input.isOptionHold { + punctuationNamePrefix = "_alt_punctuation_" + } else if input.isControlHold { + punctuationNamePrefix = "_ctrl_punctuation_" + } else if mgrPrefs.halfWidthPunctuationEnabled { + punctuationNamePrefix = "_half_punctuation_" + } else { + punctuationNamePrefix = "_punctuation_" + } + + let parser: String! = _currentMandarinParser() + + let arrCustomPunctuations: [String] = [ + punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)), + ] + let customPunctuation: String = arrCustomPunctuations.joined(separator: "") + + let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))] + let punctuation: String = arrPunctuations.joined(separator: "") + + var shouldAutoSelectCandidate: Bool = + chkKeyValidity(charCode) || ifLangModelHasUnigrams(forKey: customPunctuation) + || ifLangModelHasUnigrams(forKey: punctuation) + + if !shouldAutoSelectCandidate && input.isUpperCaseASCIILetterKey { + let letter: String! = String(format: "%@%c", "_letter_", CChar(charCode)) + if ifLangModelHasUnigrams(forKey: letter) { shouldAutoSelectCandidate = true } + } + + if shouldAutoSelectCandidate { + let candidateIndex: UInt = ctlCandidateCurrent.candidateIndexAtKeyLabelIndex(0) + if candidateIndex != UInt.max { + delegate!.keyHandler( + self, + didSelectCandidateAt: Int(candidateIndex), + ctlCandidate: ctlCandidateCurrent) + clear() + let empty = InputState.EmptyIgnoringPreviousState() + stateCallback(empty) + return handle(input: input, state: empty, stateCallback: stateCallback, errorCallback: errorCallback) + } + return true + } + } + + IME.prtDebugIntel("172A0F81") + errorCallback() + return true + } +} diff --git a/Source/Modules/ControllerModules/KeyHandler.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift similarity index 60% rename from Source/Modules/ControllerModules/KeyHandler.swift rename to Source/Modules/ControllerModules/KeyHandler_HandleInput.swift index 40236760..5b658a50 100644 --- a/Source/Modules/ControllerModules/KeyHandler.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -26,16 +26,17 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa -// MARK: - § Handle Inputs (WIP). +// MARK: - § Handle Input with States. + @objc extension KeyHandler { - func handleInputSwift( + func handle( input: keyParser, state inState: InputState, stateCallback: @escaping (InputState) -> Void, errorCallback: @escaping () -> Void ) -> Bool { let charCode: UniChar = input.charCode - var state = inState // Turn this incoming constant to variable. + var state = inState // Turn this incoming constant into variable. let inputText: String = input.inputText ?? "" let emptyState = InputState.Empty() @@ -102,14 +103,14 @@ import Cocoa // MARK: Handle Candidates. if state is InputState.ChoosingCandidate { - return handleCandidate( - state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback) + return _handleCandidateState( + state, input: input, stateCallback: stateCallback, errorCallback: errorCallback) } // MARK: Handle Associated Phrases. if state is InputState.AssociatedPhrases { - let result = handleCandidate( - state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback) + let result = _handleCandidateState( + state, input: input, stateCallback: stateCallback, errorCallback: errorCallback) if result { return true } else { @@ -221,7 +222,7 @@ import Cocoa || (input.useVerticalMode && (input.isVerticalModeOnlyChooseCandidateKey))) { if input.isSpace { - // If the spacebar is NOT set to be a selection key + // If the Space key is NOT set to be a selection key if input.isShiftHold || !mgrPrefs.chooseCandidateUsingSpace { if getBuilderCursorIndex() >= getBuilderLength() { let composingBuffer = (state as! InputState.NotEmpty).composingBuffer @@ -251,11 +252,161 @@ import Cocoa return true } - // MARK: Function Keys. + // MARK: - // MARK: Esc + if input.isESC { return _handleEscWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) } - // MARK: Still Nothing. + // MARK: Cursor backward + if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward { + return _handleBackwardWithState( + state, + input: input, + stateCallback: stateCallback, + errorCallback: errorCallback) + } + + // MARK: Cursor forward + if input.isCursorForward || input.emacsKey == vChewingEmacsKey.forward { + return _handleForwardWithState( + state, input: input, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Home + if input.isHome || input.emacsKey == vChewingEmacsKey.home { + return _handleHomeWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: End + if input.isEnd || input.emacsKey == vChewingEmacsKey.end { + return _handleEndWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Ctrl+PgLf or Shift+PgLf + if (input.isControlHold || input.isShiftHold) && (input.isOptionHold && input.isLeft) { + return _handleHomeWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Ctrl+PgRt or Shift+PgRt + if (input.isControlHold || input.isShiftHold) && (input.isOptionHold && input.isRight) { + return _handleEndWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: AbsorbedArrowKey + if input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse { + return _handleAbsorbedArrowKeyWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Backspace + if input.isBackSpace { + return _handleBackspaceWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Delete + if input.isDelete || input.emacsKey == vChewingEmacsKey.delete { + return _handleDeleteWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Enter + if input.isEnter { + return (input.isCommandHold) + ? _handleCommandEnterWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) + : _handleEnterWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: - + + // MARK: Punctuation list + if input.isSymbolMenuPhysicalKey && !input.isShiftHold { + if !input.isOptionHold { + if ifLangModelHasUnigrams(forKey: "_punctuation_list") { + if isPhoneticReadingBufferEmpty() { + insertReadingToBuilder(atCursor: "_punctuation_list") + let poppedText: String! = _popOverflowComposingTextAndWalk() + let inputting = buildInputtingState() as! InputState.Inputting + inputting.poppedText = poppedText + stateCallback(inputting) + let choosingCandidate = + _buildCandidateState(inputting, useVerticalMode: input.useVerticalMode) + stateCallback(choosingCandidate) + } else { // If there is still unfinished bpmf reading, ignore the punctuation + IME.prtDebugIntel("17446655") + errorCallback() + } + return true + } + } else { + // 得在這裡先 commit buffer,不然會導致「在摁 ESC 離開符號選單時會重複輸入上一次的組字區的內容」的不當行為。 + // 於是這裡用「模擬一次 Enter 鍵的操作」使其代為執行這個 commit buffer 的動作。 + // 這裡不需要該函數所傳回的 bool 結果,所以用「_ =」解消掉。 + _ = _handleEnterWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) + let root: SymbolNode! = SymbolNode.root + let symbolState = + InputState.SymbolTable(node: root, useVerticalMode: input.useVerticalMode) + stateCallback(symbolState) + return true + } + } + + // MARK: Punctuation + // if nothing is matched, see if it's a punctuation key for current layout. + + var punctuationNamePrefix = "" + + if input.isOptionHold { + punctuationNamePrefix = "_alt_punctuation_" + } else if input.isControlHold { + punctuationNamePrefix = "_ctrl_punctuation_" + } else if mgrPrefs.halfWidthPunctuationEnabled { + punctuationNamePrefix = "_half_punctuation_" + } else { + punctuationNamePrefix = "_punctuation_" + } + + let parser: String! = _currentMandarinParser() + let arrCustomPunctuations: [String] = [ + punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)), + ] + let customPunctuation: String = arrCustomPunctuations.joined(separator: "") + if _handlePunctuation( + customPunctuation, + state: state, + usingVerticalMode: input.useVerticalMode, + stateCallback: stateCallback, + errorCallback: errorCallback) + { + return true + } + + // if nothing is matched, see if it's a punctuation key. + let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))] + let punctuation: String = arrPunctuations.joined(separator: "") + + if _handlePunctuation( + punctuation, + state: state, + usingVerticalMode: input.useVerticalMode, + stateCallback: stateCallback, + errorCallback: errorCallback) + { + return true + } + + // 這裡不使用小麥注音 2.2 版的組字區處理方式,而是直接由詞庫負責。 + if input.isUpperCaseASCIILetterKey { + let letter: String! = String(format: "%@%c", "_letter_", CChar(charCode)) + if _handlePunctuation( + letter, + state: state, + usingVerticalMode: input.useVerticalMode, + stateCallback: stateCallback, + errorCallback: errorCallback) + { + return true + } + } + + // MARK: - Still Nothing. // Still nothing? Then we update the composing buffer. // Note that some app has strange behavior if we don't do this, // "thinking" that the key is not actually consumed. diff --git a/Source/Modules/ControllerModules/KeyHandler_Misc.swift b/Source/Modules/ControllerModules/KeyHandler_Misc.swift new file mode 100644 index 00000000..eab93dfe --- /dev/null +++ b/Source/Modules/ControllerModules/KeyHandler_Misc.swift @@ -0,0 +1,43 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Refactored from the ObjCpp-version of this class by: +// (c) 2011 and onwards The OpenVanilla Project (MIT 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 Cocoa + +// MARK: - § Misc functions. +@objc extension KeyHandler { + func _actualCandidateCursorIndex() -> Int { + var cursorIndex = getBuilderCursorIndex() + // MS Phonetics IME style, phrase is *after* the cursor. + // (i.e. the cursor is always *before* the phrase.) + if (mgrPrefs.selectPhraseAfterCursorAsCandidate + && (cursorIndex < getBuilderLength())) + || cursorIndex == 0 + { + cursorIndex += 1 + } + return cursorIndex + } +} diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index ede71207..3d111964 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -29,7 +29,7 @@ import Cocoa // MARK: - § State managements. @objc extension KeyHandler { - // MARK: 用以生成候選詞數組 + // MARK: - 用以生成候選詞數組 func _buildCandidateState( _ currentState: InputState.NotEmpty, useVerticalMode: Bool @@ -39,12 +39,12 @@ import Cocoa let state = InputState.ChoosingCandidate( composingBuffer: currentState.composingBuffer, cursorIndex: currentState.cursorIndex, - candidates: candidatesArray as! [String], + candidates: candidatesArray, useVerticalMode: useVerticalMode) return state } - // MARK: 用以處理就地新增自訂語彙時的行為 + // MARK: - 用以處理就地新增自訂語彙時的行為 func _handleMarkingState( _ state: InputState.Marking, input: keyParser, @@ -127,4 +127,378 @@ import Cocoa } return false } + + // MARK: - 標點輸入處理 + func _handlePunctuation( + _ customPunctuation: String, + state: InputState, + usingVerticalMode useVerticalMode: Bool, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !ifLangModelHasUnigrams(forKey: customPunctuation) { + return false + } + + if isPhoneticReadingBufferEmpty() { + insertReadingToBuilder(atCursor: customPunctuation) + let poppedText = _popOverflowComposingTextAndWalk() + let inputting = buildInputtingState() as! InputState.Inputting + inputting.poppedText = poppedText + stateCallback(inputting) + + if mgrPrefs.useSCPCTypingMode && isPhoneticReadingBufferEmpty() { + let candidateState = _buildCandidateState( + inputting, useVerticalMode: useVerticalMode) + if candidateState.candidates.count == 1 { + clear() + if let strPoppedText: String = candidateState.candidates.first { + let committing = + InputState.Committing(poppedText: strPoppedText) as InputState.Committing + stateCallback(committing) + let empty = InputState.Empty() + stateCallback(empty) + } else { + stateCallback(candidateState) + } + } else { + stateCallback(candidateState) + } + } + return true + } else { + // If there is still unfinished bpmf reading, ignore the punctuation + IME.prtDebugIntel("A9B69908D") + errorCallback() + stateCallback(state) + return true + } + } + + // MARK: - Enter 鍵處理 + @discardableResult func _handleEnterWithState( + _ state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } + + clear() + + let current = state as! InputState.Inputting + let composingBuffer = current.composingBuffer + + let committing = InputState.Committing(poppedText: composingBuffer) + stateCallback(committing) + let empty = InputState.Empty() + stateCallback(empty) + return true + } + + // MARK: - CMD+Enter 鍵處理 + func _handleCommandEnterWithState( + _ state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } + + let readings: [String] = _currentReadings() + let composingBuffer = + (IME.areWeUsingOurOwnPhraseEditor) + ? readings.joined(separator: "-") + : readings.joined(separator: " ") + + clear() + + let committing = InputState.Committing(poppedText: composingBuffer) + stateCallback(committing) + let empty = InputState.Empty() + stateCallback(empty) + return true + } + + // MARK: - 處理 Backspace (macOS Delete) 按鍵行為 + func _handleBackspaceWithState( + _ state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } + + if isPhoneticReadingBufferEmpty() { + if getBuilderCursorIndex() >= 0 { + deleteBuilderReadingInFrontOfCursor() + _walk() + } else { + IME.prtDebugIntel("9D69908D") + errorCallback() + stateCallback(state) + return true + } + } else { + doBackSpaceToPhoneticReadingBuffer() + } + + if isPhoneticReadingBufferEmpty() && (getBuilderLength() == 0) { + let empty = InputState.EmptyIgnoringPreviousState() + stateCallback(empty) + } else { + let inputting = buildInputtingState() as! InputState.Inputting + stateCallback(inputting) + } + return true + } + + // MARK: - 處理 PC Delete (macOS Fn+BackSpace) 按鍵行為 + func _handleDeleteWithState( + _ state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } + + if isPhoneticReadingBufferEmpty() { + if getBuilderCursorIndex() != getBuilderLength() { + deleteBuilderReadingAfterCursor() + _walk() + let inputting = buildInputtingState() as! InputState.Inputting + if inputting.composingBuffer.count == 0 { + let empty = InputState.EmptyIgnoringPreviousState() + stateCallback(empty) + } else { + stateCallback(inputting) + } + } else { + IME.prtDebugIntel("9B69938D") + errorCallback() + stateCallback(state) + } + } else { + IME.prtDebugIntel("9C69908D") + errorCallback() + stateCallback(state) + } + + return true + } + + // MARK: - 處理與當前文字輸入排版前後方向呈 90 度的那兩個方向鍵的按鍵行為 + func _handleAbsorbedArrowKeyWithState( + _ state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } + if !isPhoneticReadingBufferEmpty() { + IME.prtDebugIntel("9B6F908D") + errorCallback() + } + stateCallback(state) + return true + } + + // MARK: - 處理 Home 鍵行為 + func _handleHomeWithState( + _ state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } + + if !isPhoneticReadingBufferEmpty() { + IME.prtDebugIntel("ABC44080") + errorCallback() + stateCallback(state) + return true + } + + if getBuilderCursorIndex() != 0 { + setBuilderCursorIndex(0) + let inputting = buildInputtingState() as! InputState.Inputting + stateCallback(inputting) + } else { + IME.prtDebugIntel("66D97F90") + errorCallback() + stateCallback(state) + } + + return true + } + + // MARK: - 處理 End 鍵行為 + func _handleEndWithState( + _ state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } + + if !isPhoneticReadingBufferEmpty() { + IME.prtDebugIntel("9B69908D") + errorCallback() + stateCallback(state) + return true + } + + if getBuilderCursorIndex() != getBuilderLength() { + setBuilderCursorIndex(getBuilderLength()) + let inputting = buildInputtingState() as! InputState.Inputting + stateCallback(inputting) + } else { + IME.prtDebugIntel("9B69908E") + errorCallback() + stateCallback(state) + } + + return true + } + + // MARK: - 處理 Esc 鍵行為 + func _handleEscWithState( + _ state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { return false } + + let escToClearInputBufferEnabled: Bool = mgrPrefs.escToCleanInputBuffer + + if escToClearInputBufferEnabled { + // 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() + let empty = InputState.EmptyIgnoringPreviousState() + stateCallback(empty) + } else { + // If reading is not empty, we cancel the reading. + if !isPhoneticReadingBufferEmpty() { + clearPhoneticReadingBuffer() + if getBuilderLength() == 0 { + let empty = InputState.Empty() + stateCallback(empty) + } else { + let inputting = buildInputtingState() + stateCallback(inputting) + } + } + } + return true + } + // MARK: - 處理向前方向鍵的行為 + func _handleForwardWithState( + _ state: InputState, + input: keyParser, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + + if !(state is InputState.Inputting) { return false } + + if !isPhoneticReadingBufferEmpty() { + IME.prtDebugIntel("B3BA5257") + errorCallback() + stateCallback(state) + return true + } + + let currentState = state as! InputState.Inputting + + if input.isShiftHold { + // Shift + Right + if currentState.cursorIndex < (currentState.composingBuffer as NSString).length { + let nextPosition = (currentState.composingBuffer as NSString).nextUtf16Position( + for: Int(currentState.cursorIndex)) + let marking: InputState.Marking! = InputState.Marking( + composingBuffer: currentState.composingBuffer, + cursorIndex: currentState.cursorIndex, + markerIndex: UInt(nextPosition), + readings: _currentReadings()) + marking.tooltipForInputting = currentState.tooltip + stateCallback(marking) + } else { + IME.prtDebugIntel("BB7F6DB9") + errorCallback() + stateCallback(state) + } + } else { + if getBuilderCursorIndex() < getBuilderLength() { + setBuilderCursorIndex(getBuilderCursorIndex() + 1) + let inputting = buildInputtingState() as! InputState.Inputting + stateCallback(inputting) + } else { + IME.prtDebugIntel("A96AAD58") + errorCallback() + stateCallback(state) + } + } + return true + } + + // MARK: - 處理向後方向鍵的行為 + func _handleBackwardWithState( + _ state: InputState, + input: keyParser, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + + if !(state is InputState.Inputting) { return false } + + if !isPhoneticReadingBufferEmpty() { + IME.prtDebugIntel("6ED95318") + errorCallback() + stateCallback(state) + return true + } + + let currentState = state as! InputState.Inputting + + if input.isShiftHold { + // Shift + left + if currentState.cursorIndex > 0 { + let previousPosition = (currentState.composingBuffer as NSString).previousUtf16Position( + for: Int(currentState.cursorIndex)) + let marking: InputState.Marking! = InputState.Marking( + composingBuffer: currentState.composingBuffer, + cursorIndex: currentState.cursorIndex, + markerIndex: UInt(previousPosition), + readings: _currentReadings()) + marking.tooltipForInputting = currentState.tooltip + stateCallback(marking) + } else { + IME.prtDebugIntel("D326DEA3") + errorCallback() + stateCallback(state) + } + } else { + if getBuilderCursorIndex() > 0 { + setBuilderCursorIndex(getBuilderCursorIndex() - 1) + let inputting = buildInputtingState() as! InputState.Inputting + stateCallback(inputting) + } else { + IME.prtDebugIntel("7045E6F3") + errorCallback() + stateCallback(state) + } + } + return true + } + } diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index fad18f44..ccdf0318 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 5B2DB17027AF6891006D874E /* data-cht.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B2DB16E27AF6891006D874E /* data-cht.txt */; }; 5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */; }; 5B5E535227EF261400C6AA1E /* IME.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E535127EF261400C6AA1E /* IME.swift */; }; + 5B61B0CA280BEFD4002E3CFA /* KeyHandler_Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B61B0C9280BEFD4002E3CFA /* KeyHandler_Misc.swift */; }; 5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */; }; 5B62A32F27AE78B000A19448 /* CoreLM.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A32D27AE78B000A19448 /* CoreLM.mm */; }; 5B62A33227AE792F00A19448 /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33127AE792F00A19448 /* InputSourceHelper.swift */; }; @@ -30,8 +31,10 @@ 5B707CE827D9F4590099EF99 /* OpenCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B707CE727D9F4590099EF99 /* OpenCCBridge.swift */; }; 5B707CEC27D9F4870099EF99 /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = 5B707CEB27D9F4870099EF99 /* OpenCC */; }; 5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; }; + 5B782EC4280C243C007276DE /* KeyHandler_HandleCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */; }; + 5B782EC6280C2F4B007276DE /* KeyHandler_BuildInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B782EC5280C2F4B007276DE /* KeyHandler_BuildInput.swift */; }; 5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; }; - 5B7F225D2808501000DDD3CB /* KeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B7F225C2808501000DDD3CB /* KeyHandler.swift */; }; + 5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B7F225C2808501000DDD3CB /* KeyHandler_HandleInput.swift */; }; 5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */; }; 5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */; }; 5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */; }; @@ -189,6 +192,7 @@ 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = ""; }; 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandler_States.swift; sourceTree = ""; }; 5B5E535127EF261400C6AA1E /* IME.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IME.swift; sourceTree = ""; }; + 5B61B0C9280BEFD4002E3CFA /* KeyHandler_Misc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandler_Misc.swift; sourceTree = ""; }; 5B62A32627AE77BB00A19448 /* LMConsolidator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMConsolidator.h; sourceTree = ""; }; 5B62A32727AE77BB00A19448 /* LMConsolidator.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LMConsolidator.mm; sourceTree = ""; }; 5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FSEventStreamHelper.swift; sourceTree = ""; }; @@ -209,9 +213,11 @@ 5B7111C727DEF9FF00444310 /* UserSymbolLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserSymbolLM.h; sourceTree = ""; }; 5B73FB5427B2BD6900E9BF49 /* PhraseEditor-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "PhraseEditor-Info.plist"; path = "UserPhraseEditor/PhraseEditor-Info.plist"; sourceTree = SOURCE_ROOT; }; 5B73FB5F27B2BE1300E9BF49 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandler_HandleCandidate.swift; sourceTree = ""; }; + 5B782EC5280C2F4B007276DE /* KeyHandler_BuildInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandler_BuildInput.swift; sourceTree = ""; }; 5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmPrefWindow.xib; sourceTree = ""; }; 5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmPrefWindow.strings; sourceTree = ""; }; - 5B7F225C2808501000DDD3CB /* KeyHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandler.swift; sourceTree = ""; }; + 5B7F225C2808501000DDD3CB /* KeyHandler_HandleInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandler_HandleInput.swift; sourceTree = ""; }; 5B8F43ED27C9BC220069AC27 /* SymbolLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SymbolLM.h; sourceTree = ""; }; 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = suiPrefPaneGeneral.swift; sourceTree = ""; }; 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = suiPrefPaneKeyboard.swift; sourceTree = ""; }; @@ -427,7 +433,10 @@ D461B791279DAC010070E734 /* InputState.swift */, D4E569DA27A34CC100AC2CEF /* KeyHandler.h */, D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */, - 5B7F225C2808501000DDD3CB /* KeyHandler.swift */, + 5B782EC5280C2F4B007276DE /* KeyHandler_BuildInput.swift */, + 5B7F225C2808501000DDD3CB /* KeyHandler_HandleInput.swift */, + 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */, + 5B61B0C9280BEFD4002E3CFA /* KeyHandler_Misc.swift */, 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */, D456576D279E4F7B00DF6BC9 /* KeyParser.swift */, 6ACC3D3E27914F2400F1B140 /* KeyValueBlobReader.cpp */, @@ -1067,7 +1076,7 @@ D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */, 5BA9FD4527FEF3C9002DE248 /* ToolbarItemStyleViewController.swift in Sources */, 5BA9FD4127FEF3C8002DE248 /* PreferencesStyle.swift in Sources */, - 5B7F225D2808501000DDD3CB /* KeyHandler.swift in Sources */, + 5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */, 5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */, 6ACC3D442793701600F1B140 /* ParselessPhraseDB.cpp in Sources */, D461B792279DAC010070E734 /* InputState.swift in Sources */, @@ -1082,6 +1091,7 @@ 5B62A32F27AE78B000A19448 /* CoreLM.mm in Sources */, 5BE78BE027B38804005EA1BE /* LMConsolidator.mm in Sources */, D456576E279E4F7B00DF6BC9 /* KeyParser.swift in Sources */, + 5B782EC6280C2F4B007276DE /* KeyHandler_BuildInput.swift in Sources */, 5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */, 5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */, 5BA9FD4327FEF3C8002DE248 /* Preferences.swift in Sources */, @@ -1098,6 +1108,7 @@ D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */, 5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */, 5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */, + 5B782EC4280C243C007276DE /* KeyHandler_HandleCandidate.swift in Sources */, 5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */, 5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */, 5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */, @@ -1107,6 +1118,7 @@ 5B5E535227EF261400C6AA1E /* IME.swift in Sources */, 5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, + 5B61B0CA280BEFD4002E3CFA /* KeyHandler_Misc.swift in Sources */, 5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */, 5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */, 5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */,