diff --git a/Source/3rdParty/OVMandarin/Mandarin.h b/Source/3rdParty/OVMandarin/Mandarin.h index 61697c1c..03a46c08 100644 --- a/Source/3rdParty/OVMandarin/Mandarin.h +++ b/Source/3rdParty/OVMandarin/Mandarin.h @@ -210,7 +210,7 @@ typedef BopomofoSyllable BPMF; typedef std::map> BopomofoKeyToComponentMap; typedef std::map BopomofoComponentToKeyMap; -class BopomofoKeyboardLayout +extern "C" class BopomofoKeyboardLayout { public: static const BopomofoKeyboardLayout *StandardLayout(); @@ -438,7 +438,7 @@ class BopomofoKeyboardLayout BopomofoComponentToKeyMap m_componentToKey; }; -class BopomofoReadingBuffer +extern "C" class BopomofoReadingBuffer { public: explicit BopomofoReadingBuffer(const BopomofoKeyboardLayout *layout) : layout_(layout), pinyin_mode_(false) diff --git a/Source/Modules/ControllerModules/KeyHandler.h b/Source/Modules/ControllerModules/KeyHandler.h index 9e1db717..7bbbc40e 100644 --- a/Source/Modules/ControllerModules/KeyHandler.h +++ b/Source/Modules/ControllerModules/KeyHandler.h @@ -36,6 +36,14 @@ extern InputMode imeModeCHT; extern InputMode imeModeCHS; extern InputMode imeModeNULL; +struct BufferStatePackage +{ + NSString *composedText; + NSInteger cursorIndex; + NSString *resultOfBefore; + NSString *resultOfAfter; +}; + @class KeyHandler; @protocol KeyHandlerDelegate @@ -47,21 +55,48 @@ 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)syncWithPreferences; - (void)fixNodeWithValue:(NSString *)value NS_SWIFT_NAME(fixNode(value:)); - (void)clear; -- (InputState *)buildInputtingState; - (nullable InputState *)buildAssociatePhraseStateWithKey:(NSString *)key useVerticalMode:(BOOL)useVerticalMode; @property(strong, nonatomic) InputMode inputMode; @property(weak, nonatomic) id delegate; + +// The following items need to be exposed to Swift: +- (void)_walk; +- (NSString *)_popOverflowComposingTextAndWalk; +- (NSArray *)_currentReadings; + +- (BOOL)checkWhetherToneMarkerConfirmsPhoneticReadingBuffer; +- (BOOL)chkKeyValidity:(UniChar)value; +- (BOOL)ifLangModelHasUnigramsForKey:(NSString *)reading; +- (BOOL)isPhoneticReadingBufferEmpty; +- (BOOL)isPrintable:(UniChar)charCode; +- (NSArray *)getCandidatesArray; +- (NSInteger)getBuilderCursorIndex; +- (NSInteger)getBuilderLength; +- (NSInteger)getPackagedCursorIndex; +- (NSString *)getComposedText; +- (NSString *)getCompositionFromPhoneticReadingBuffer; +- (NSString *)getStrLocationResult:(BOOL)isAfter NS_SWIFT_NAME(getStrLocationResult(isAfter:)); +- (NSString *)getSyllableCompositionFromPhoneticReadingBuffer; +- (void)clearPhoneticReadingBuffer; +- (void)combinePhoneticReadingBufferKey:(UniChar)charCode; +- (void)createNewBuilder; +- (void)dealWithOverrideModelSuggestions; +- (void)deleteBuilderReadingAfterCursor; +- (void)deleteBuilderReadingInFrontOfCursor; +- (void)doBackSpaceToPhoneticReadingBuffer; +- (void)ensurePhoneticParser; +- (void)insertReadingToBuilderAtCursor:(NSString *)reading; +- (void)packageBufferStateMaterials; +- (void)removeBuilderAndReset:(BOOL)shouldReset; +- (void)setBuilderCursorIndex:(NSInteger)value; +- (void)setInputModesToLM:(BOOL)isCHS; +- (void)syncBaseLMPrefs; + @end NS_ASSUME_NONNULL_END diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm index 5bcba51a..503f188e 100644 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ b/Source/Modules/ControllerModules/KeyHandler.mm @@ -37,8 +37,19 @@ InputMode imeModeCHS = ctlInputMethod.kIMEModeCHS; InputMode imeModeCHT = ctlInputMethod.kIMEModeCHT; InputMode imeModeNULL = ctlInputMethod.kIMEModeNULL; +typedef vChewing::LMInstantiator BaseLM; +typedef vChewing::UserOverrideModel UserOverrideLM; +typedef Gramambular::BlockReadingBuilder BlockBuilder; +typedef Mandarin::BopomofoReadingBuffer PhoneticBuffer; + static const double kEpsilon = 0.000001; +NSString *packagedComposedText; +NSInteger packagedCursorIndex; +NSString *packagedResultOfBefore; +NSString *packagedResultOfAfter; + +// NON-SWIFTIFIABLE static double FindHighestScore(const std::vector &nodes, double epsilon) { double highestScore = 0.0; @@ -51,6 +62,7 @@ static double FindHighestScore(const std::vector &nodes return highestScore + epsilon; } +// NON-SWIFTIFIABLE class NodeAnchorDescendingSorter { public: @@ -66,19 +78,20 @@ class NodeAnchorDescendingSorter static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; #endif +// NON-SWIFTIFIABLE @implementation KeyHandler { // the reading buffer that takes user input - Mandarin::BopomofoReadingBuffer *_bpmfReadingBuffer; + PhoneticBuffer *_bpmfReadingBuffer; // language model - vChewing::LMInstantiator *_languageModel; + BaseLM *_languageModel; // user override model - vChewing::UserOverrideModel *_userOverrideModel; + UserOverrideLM *_userOverrideModel; // the grid (lattice) builder for the unigrams (and bigrams) - Gramambular::BlockReadingBuilder *_builder; + BlockBuilder *_builder; // latest walked path (trellis) using the Viterbi algorithm std::vector _walkedNodes; @@ -86,126 +99,75 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSString *_inputMode; } -//@synthesize inputMode = _inputMode; @synthesize delegate = _delegate; +// NON-SWIFTIFIABLE DUE TO VARIABLE AVAILABLE ACCESSIBILITY RANGE. +// VARIABLE: "_inputMode" - (NSString *)inputMode { return _inputMode; } +// NON-SWIFTIFIABLE - (BOOL)isBuilderEmpty { return (_builder->grid().width() == 0); } +// NON-SWIFTIFIABLE DUE TO VARIABLE AVAILABLE ACCESSIBILITY RANGE. +// VARIABLE: "_inputMode" - (void)setInputMode:(NSString *)value { - NSString *newInputMode; - vChewing::LMInstantiator *newLanguageModel; - vChewing::UserOverrideModel *newUserOverrideModel; - + // 下面這句的「isKindOfClass」是做類型檢查, + // 為了應對出現輸入法 plist 被改壞掉這樣的極端情況。 BOOL isCHS = [value isKindOfClass:[NSString class]] && [value isEqual:imeModeCHS]; - newInputMode = isCHS ? imeModeCHS : imeModeCHT; - newLanguageModel = isCHS ? [mgrLangModel lmCHS] : [mgrLangModel lmCHT]; - newUserOverrideModel = isCHS ? [mgrLangModel userOverrideModelCHS] : [mgrLangModel userOverrideModelCHT]; + // 緊接著將新的簡繁輸入模式提報給 ctlInputMethod: + ctlInputMethod.currentInputMode = isCHS ? imeModeCHS : imeModeCHT; + mgrPrefs.mostRecentInputMode = ctlInputMethod.currentInputMode; - // Report the current Input Mode to ctlInputMethod: - ctlInputMethod.currentInputMode = newInputMode; - - // Synchronize the sub-languageModel state settings to the new LM. - newLanguageModel->setPhraseReplacementEnabled(mgrPrefs.phraseReplacementEnabled); - newLanguageModel->setSymbolEnabled(mgrPrefs.symbolInputEnabled); - newLanguageModel->setCNSEnabled(mgrPrefs.cns11643Enabled); - - // Only apply the changes if the value is changed - if (![_inputMode isEqualToString:newInputMode]) + // 拿當前的 _inputMode 與 ctlInputMethod 的提報結果對比,不同的話則套用新設定: + if (![_inputMode isEqualToString:ctlInputMethod.currentInputMode]) { - _inputMode = newInputMode; - _languageModel = newLanguageModel; - _userOverrideModel = newUserOverrideModel; + // Reinitiate language models if necessary + [self setInputModesToLM:isCHS]; - if (_builder) - { - delete _builder; - _builder = new Gramambular::BlockReadingBuilder(_languageModel); - _builder->setJoinSeparator("-"); - } + // Synchronize the sub-languageModel state settings to the new LM. + [self syncBaseLMPrefs]; - if (!_bpmfReadingBuffer->isEmpty()) - _bpmfReadingBuffer->clear(); + [self removeBuilderAndReset:YES]; + + if (![self isPhoneticReadingBufferEmpty]) + [self clearPhoneticReadingBuffer]; } + _inputMode = ctlInputMethod.currentInputMode; } +// NON-SWIFTIFIABLE: Required by an ObjC(pp)-based class. - (void)dealloc { // clean up everything if (_bpmfReadingBuffer) delete _bpmfReadingBuffer; if (_builder) - delete _builder; + [self removeBuilderAndReset:NO]; } +// NON-SWIFTIFIABLE: Not placeable in swift extensions. - (instancetype)init { self = [super init]; if (self) { - _bpmfReadingBuffer = new Mandarin::BopomofoReadingBuffer(Mandarin::BopomofoKeyboardLayout::StandardLayout()); - - // create the lattice builder - _languageModel = [mgrLangModel lmCHT]; - _languageModel->setPhraseReplacementEnabled(mgrPrefs.phraseReplacementEnabled); - _languageModel->setCNSEnabled(mgrPrefs.cns11643Enabled); - _languageModel->setSymbolEnabled(mgrPrefs.symbolInputEnabled); - _userOverrideModel = [mgrLangModel userOverrideModelCHT]; - - _builder = new Gramambular::BlockReadingBuilder(_languageModel); - - // each Mandarin syllable is separated by a hyphen - _builder->setJoinSeparator("-"); - _inputMode = imeModeCHT; + [self ensurePhoneticParser]; + [self setInputMode:ctlInputMethod.currentInputMode]; } return self; } -- (void)syncWithPreferences -{ - switch (mgrPrefs.mandarinParser) - { - case MandarinParserOfStandard: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); - break; - case MandarinParserOfEten: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout()); - break; - case MandarinParserOfHsu: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout()); - break; - case MandarinParserOfEen26: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout()); - break; - case MandarinParserOfIBM: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout()); - break; - case MandarinParserOfMiTAC: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout()); - break; - case MandarinParserOfFakeSeigyou: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout()); - break; - case MandarinParserOfHanyuPinyin: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout()); - break; - default: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); - mgrPrefs.mandarinParser = MandarinParserOfStandard; - } -} - +// 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) @@ -237,1273 +199,43 @@ 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]; } } +// NON-SWIFTIFIABLE - (void)clear { - _bpmfReadingBuffer->clear(); + [self clearPhoneticReadingBuffer]; _builder->clear(); _walkedNodes.clear(); } -- (std::string)_currentMandarinParser -{ - return std::string(mgrPrefs.mandarinParserName.UTF8String) + std::string("_"); -} - -// 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 && _bpmfReadingBuffer->isValidKey((char)charCode)) - { - _bpmfReadingBuffer->combineKey((char)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 = _bpmfReadingBuffer->hasToneMarker(); - 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 |= (!_bpmfReadingBuffer->isEmpty() && ([input isSpace] || [input isEnter])); - if (composeReading) - { - // combine the reading - std::string reading = _bpmfReadingBuffer->syllable().composedString(); - - // see if we have an unigram for this - if (!_languageModel->hasUnigramsForKey(reading)) - { - [IME prtDebugIntel:@"B49C0979"]; - errorCallback(); - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - return YES; - } - - // and insert it into the lattice - _builder->insertReadingAtCursor(reading); - - // then walk the lattice - NSString *poppedText = [self _popOverflowComposingTextAndWalk]; - - // get user override model suggestion - std::string overrideValue = (mgrPrefs.useSCPCTypingMode) - ? "" - : _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), - [[NSDate date] timeIntervalSince1970]); - - if (!overrideValue.empty()) - { - size_t cursorIndex = [self _actualCandidateCursorIndex]; - std::vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); - double highestScore = FindHighestScore(nodes, kEpsilon); - _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, - static_cast(highestScore)); - } - - // then update the text - _bpmfReadingBuffer->clear(); - - 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 (_bpmfReadingBuffer->isEmpty() && [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 (_builder->cursorIndex() >= _builder->length()) - { - 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 (_languageModel->hasUnigramsForKey(" ")) - { - _builder->insertReadingAtCursor(" "); - 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 (_languageModel->hasUnigramsForKey("_punctuation_list")) - { - if (_bpmfReadingBuffer->isEmpty()) - { - _builder->insertReadingAtCursor(string("_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. - - std::string punctuationNamePrefix; - - if ([input isOptionHold]) - punctuationNamePrefix = std::string("_alt_punctuation_"); - else if ([input isControlHold]) - punctuationNamePrefix = std::string("_ctrl_punctuation_"); - else if (mgrPrefs.halfWidthPunctuationEnabled) - punctuationNamePrefix = std::string("_half_punctuation_"); - else - punctuationNamePrefix = std::string("_punctuation_"); - - std::string parser = [self _currentMandarinParser]; - std::string customPunctuation = punctuationNamePrefix + parser + std::string(1, (char)charCode); - 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. - std::string punctuation = punctuationNamePrefix + std::string(1, (char)charCode); - if ([self _handlePunctuation:punctuation - state:state - usingVerticalMode:input.useVerticalMode - stateCallback:stateCallback - errorCallback:errorCallback]) - return YES; - - // Lukhnos 這裡的處理反而會使得 Apple 倚天注音動態鍵盤佈局「敲不了半形大寫英文」的缺點曝露無疑,所以注釋掉。 - // 至於他試圖用這種處理來解決的上游 UPR293 - // 的問題,其實針對詞庫檔案的排序做點手腳就可以解決。威注音本來也就是這麼做的。 - if (/*[state isKindOfClass:[InputStateNotEmpty class]] && */ [input isUpperCaseASCIILetterKey]) - { - std::string letter = std::string("_letter_") + std::string(1, (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]] || !_bpmfReadingBuffer->isEmpty()) - { - [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 (!_bpmfReadingBuffer->isEmpty()) - { - _bpmfReadingBuffer->clear(); - 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 (!_bpmfReadingBuffer->isEmpty()) - { - [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 (!_bpmfReadingBuffer->isEmpty()) - { - [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 (!_bpmfReadingBuffer->isEmpty()) - { - [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 (!_bpmfReadingBuffer->isEmpty()) - { - [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 (!_bpmfReadingBuffer->isEmpty()) - { - [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 (_bpmfReadingBuffer->isEmpty()) - { - if (_builder->cursorIndex()) - { - _builder->deleteReadingBeforeCursor(); - [self _walk]; - } - else - { - [IME prtDebugIntel:@"9D69908D"]; - errorCallback(); - stateCallback(state); - return YES; - } - } - else - _bpmfReadingBuffer->backspace(); - - if (_bpmfReadingBuffer->isEmpty() && !_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 (_bpmfReadingBuffer->isEmpty()) - { - 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:(std::string)customPunctuation - state:(InputState *)state - usingVerticalMode:(BOOL)useVerticalMode - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - if (!_languageModel->hasUnigramsForKey(customPunctuation)) - return NO; - - NSString *poppedText; - if (_bpmfReadingBuffer->isEmpty()) - { - _builder->insertReadingAtCursor(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 && _bpmfReadingBuffer->isEmpty()) - { - 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)_handleMarkingState:(InputStateMarking *)state - input:(keyParser *)input - stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback -{ - - if ([input isESC]) - { - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - return YES; - } - - // Enter - if ([input isEnter]) - { - if (![self.delegate keyHandler:self didRequestWriteUserPhraseWithState:state]) - { - [IME prtDebugIntel:@"5B69CC8D"]; - errorCallback(); - return YES; - } - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); - return YES; - } - - // Shift + left - if (([input isCursorBackward] || input.emacsKey == vChewingEmacsKeyBackward) && ([input isShiftHold])) - { - NSUInteger index = state.markerIndex; - if (index > 0) - { - index = [state.composingBuffer previousUtf16PositionFor:index]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer - cursorIndex:state.cursorIndex - markerIndex:index - readings:state.readings]; - marking.tooltipForInputting = state.tooltipForInputting; - - if (marking.markedRange.length == 0) - { - InputState *inputting = [marking convertToInputting]; - stateCallback(inputting); - } - else - stateCallback(marking); - } - else - { - [IME prtDebugIntel:@"1149908D"]; - errorCallback(); - stateCallback(state); - } - return YES; - } - - // Shift + Right - if (([input isCursorForward] || input.emacsKey == vChewingEmacsKeyForward) && ([input isShiftHold])) - { - NSUInteger index = state.markerIndex; - if (index < state.composingBuffer.length) - { - index = [state.composingBuffer nextUtf16PositionFor:index]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer - cursorIndex:state.cursorIndex - markerIndex:index - readings:state.readings]; - marking.tooltipForInputting = state.tooltipForInputting; - if (marking.markedRange.length == 0) - { - InputState *inputting = [marking convertToInputting]; - stateCallback(inputting); - } - else - stateCallback(marking); - } - else - { - [IME prtDebugIntel:@"9B51408D"]; - errorCallback(); - stateCallback(state); - } - return YES; - } - return NO; -} - -- (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) - { - std::string punctuationNamePrefix; - if ([input isOptionHold]) - punctuationNamePrefix = std::string("_alt_punctuation_"); - else if ([input isControlHold]) - punctuationNamePrefix = std::string("_ctrl_punctuation_"); - else if (mgrPrefs.halfWidthPunctuationEnabled) - punctuationNamePrefix = std::string("_half_punctuation_"); - else - punctuationNamePrefix = std::string("_punctuation_"); - - std::string parser = [self _currentMandarinParser]; - std::string customPunctuation = punctuationNamePrefix + parser + std::string(1, (char)charCode); - std::string punctuation = punctuationNamePrefix + std::string(1, (char)charCode); - - BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) || - _languageModel->hasUnigramsForKey(customPunctuation) || - _languageModel->hasUnigramsForKey(punctuation); - - if (!shouldAutoSelectCandidate && [input isUpperCaseASCIILetterKey]) - { - std::string letter = std::string("_letter_") + std::string(1, (char)charCode); - if (_languageModel->hasUnigramsForKey(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 -- (InputStateInputting *)buildInputtingState +// NON-SWIFTIFIABLE +- (void)packageBufferStateMaterials { + // We gather the data through this function, package it, + // and sent it to our Swift extension to build the InputState.Inputting there. + // Otherwise, ObjC++ always bugs for "expecting a type". + // "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]; + + NSString *resultOfBefore = @""; + NSString *resultOfAfter = @""; + for (std::vector::iterator wi = _walkedNodes.begin(), we = _walkedNodes.end(); wi != we; ++wi) { @@ -1513,7 +245,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 @@ -1550,30 +282,20 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } if (builderCursorIndex == 0) { - tooltip = [NSString - stringWithFormat:NSLocalizedString(@"Cursor is before \"%@\".", @""), - [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex] - .c_str()]]; + resultOfBefore = + [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex].c_str()]; } else if (builderCursorIndex >= _builder->readings().size()) { - tooltip = [NSString - stringWithFormat:NSLocalizedString(@"Cursor is after \"%@\".", @""), - [NSString - stringWithUTF8String:_builder - ->readings()[_builder->readings().size() - - 1] - .c_str()]]; + resultOfAfter = [NSString + stringWithUTF8String:_builder->readings()[_builder->readings().size() - 1].c_str()]; } else { - tooltip = [NSString - stringWithFormat:NSLocalizedString(@"Cursor is between \"%@\" and \"%@\".", @""), - [NSString - stringWithUTF8String:_builder->readings()[builderCursorIndex - 1] - .c_str()], - [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex] - .c_str()]]; + resultOfBefore = + [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex].c_str()]; + resultOfAfter = + [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex - 1].c_str()]; } } } @@ -1585,17 +307,39 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // and insert the reading text (the Mandarin syllable) in between them; // the reading text is what the user is typing NSString *head = [composingBuffer substringToIndex:composedStringCursorIndex]; - NSString *reading = [NSString stringWithUTF8String:_bpmfReadingBuffer->composedString().c_str()]; + NSString *reading = [self getCompositionFromPhoneticReadingBuffer]; NSString *tail = [composingBuffer substringFromIndex:composedStringCursorIndex]; NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; NSInteger cursorIndex = composedStringCursorIndex + [reading length]; - InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText - cursorIndex:cursorIndex]; - newState.tooltip = tooltip; - return newState; + packagedComposedText = composedText; + packagedCursorIndex = cursorIndex; + packagedResultOfBefore = resultOfBefore; + packagedResultOfAfter = resultOfAfter; } +// NON-SWIFTIFIABLE DUE TO VARIABLE AVAILABLE ACCESSIBILITY RANGE. +- (NSString *)getStrLocationResult:(BOOL)isAfter +{ + if (isAfter) + return packagedResultOfAfter; + else + return packagedResultOfBefore; +} + +// NON-SWIFTIFIABLE DUE TO VARIABLE AVAILABLE ACCESSIBILITY RANGE. +- (NSString *)getComposedText +{ + return packagedComposedText; +} + +// NON-SWIFTIFIABLE DUE TO VARIABLE AVAILABLE ACCESSIBILITY RANGE. +- (NSInteger)getPackagedCursorIndex +{ + return packagedCursorIndex; +} + +// NON-SWIFTIFIABLE - (void)_walk { // retrieve the most likely trellis, i.e. a Maximum Likelihood Estimation @@ -1622,15 +366,16 @@ 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, // but because the Viterbi algorithm has a complexity of O(N^2), // the walk will become slower as the number of nodes increase, - // therefore we need to "pop out" overflown text -- they usually + // 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 at front will - // be popped out + // the user type along, the already composed text in the rear side + // of the buffer will be committed (i.e. "popped out"). NSString *poppedText = @""; NSInteger composingBufferSize = mgrPrefs.composingBufferSize; @@ -1649,53 +394,17 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return poppedText; } -- (InputStateChoosingCandidate *)_buildCandidateState:(InputStateNotEmpty *)currentState - useVerticalMode:(BOOL)useVerticalMode +// NON-SWIFTIFIABLE +- (NSArray *)_currentReadings { - NSMutableArray *candidatesArray = [[NSMutableArray alloc] init]; - - size_t 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 - stable_sort(nodes.begin(), nodes.end(), NodeAnchorDescendingSorter()); - - // then use the C++ trick to retrieve the candidates for each node at/crossing the cursor - for (std::vector::iterator ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) - { - const std::vector &candidates = (*ni).node->candidates(); - for (std::vector::const_iterator ci = candidates.begin(), ce = candidates.end(); - ci != ce; ++ci) - [candidatesArray addObject:[NSString stringWithUTF8String:(*ci).value.c_str()]]; - } - - InputStateChoosingCandidate *state = - [[InputStateChoosingCandidate alloc] initWithComposingBuffer:currentState.composingBuffer - cursorIndex:currentState.cursorIndex - candidates:candidatesArray - useVerticalMode:useVerticalMode]; - return state; -} - -- (size_t)_actualCandidateCursorIndex -{ - 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; -} - -- (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()]]; return readingsArray; } +// NON-SWIFTIFIABLE - (nullable InputState *)buildAssociatePhraseStateWithKey:(NSString *)key useVerticalMode:(BOOL)useVerticalMode { std::string cppKey = std::string(key.UTF8String); @@ -1715,4 +424,208 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return nil; } +#pragma mark - 必須用 ObjCpp 處理的部分: Mandarin + +- (BOOL)chkKeyValidity:(UniChar)charCode +{ + return _bpmfReadingBuffer->isValidKey((char)charCode); +} + +- (BOOL)isPhoneticReadingBufferEmpty +{ + return _bpmfReadingBuffer->isEmpty(); +} + +- (void)clearPhoneticReadingBuffer +{ + _bpmfReadingBuffer->clear(); +} + +- (void)combinePhoneticReadingBufferKey:(UniChar)charCode +{ + _bpmfReadingBuffer->combineKey((char)charCode); +} + +- (BOOL)checkWhetherToneMarkerConfirmsPhoneticReadingBuffer +{ + return _bpmfReadingBuffer->hasToneMarker(); +} + +- (NSString *)getSyllableCompositionFromPhoneticReadingBuffer +{ + return [NSString stringWithUTF8String:_bpmfReadingBuffer->syllable().composedString().c_str()]; +} + +- (void)doBackSpaceToPhoneticReadingBuffer +{ + _bpmfReadingBuffer->backspace(); +} + +- (NSString *)getCompositionFromPhoneticReadingBuffer +{ + return [NSString stringWithUTF8String:_bpmfReadingBuffer->composedString().c_str()]; +} + +- (void)ensurePhoneticParser +{ + if (_bpmfReadingBuffer) + { + switch (mgrPrefs.mandarinParser) + { + case MandarinParserOfStandard: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); + break; + case MandarinParserOfEten: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout()); + break; + case MandarinParserOfHsu: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout()); + break; + case MandarinParserOfEen26: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout()); + break; + case MandarinParserOfIBM: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout()); + break; + case MandarinParserOfMiTAC: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout()); + break; + case MandarinParserOfFakeSeigyou: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout()); + break; + case MandarinParserOfHanyuPinyin: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout()); + break; + default: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); + mgrPrefs.mandarinParser = MandarinParserOfStandard; + } + } + else + { + _bpmfReadingBuffer = new Mandarin::BopomofoReadingBuffer(Mandarin::BopomofoKeyboardLayout::StandardLayout()); + } +} + +#pragma mark - 必須用 ObjCpp 處理的部分: Gramambular 等 + +- (void)removeBuilderAndReset:(BOOL)shouldReset +{ + if (_builder) + { + delete _builder; + if (shouldReset) + [self createNewBuilder]; + } + else if (shouldReset) + [self createNewBuilder]; +} + +- (void)createNewBuilder +{ + _builder = new Gramambular::BlockReadingBuilder(_languageModel); + // Each Mandarin syllable is separated by a hyphen. + _builder->setJoinSeparator("-"); +} + +- (void)setInputModesToLM:(BOOL)isCHS +{ + _languageModel = isCHS ? [mgrLangModel lmCHS] : [mgrLangModel lmCHT]; + _userOverrideModel = isCHS ? [mgrLangModel userOverrideModelCHS] : [mgrLangModel userOverrideModelCHT]; +} + +- (void)syncBaseLMPrefs +{ + if (_languageModel) + { + _languageModel->setPhraseReplacementEnabled(mgrPrefs.phraseReplacementEnabled); + _languageModel->setSymbolEnabled(mgrPrefs.symbolInputEnabled); + _languageModel->setCNSEnabled(mgrPrefs.cns11643Enabled); + } +} + +// ---- + +- (BOOL)ifLangModelHasUnigramsForKey:(NSString *)reading +{ + return _languageModel->hasUnigramsForKey((std::string)[reading UTF8String]); +} + +- (void)insertReadingToBuilderAtCursor:(NSString *)reading +{ + _builder->insertReadingAtCursor((std::string)[reading UTF8String]); +} + +- (void)dealWithOverrideModelSuggestions +{ + // 這一整段都太 C++ 且只出現一次,就整個端過來了。 + // 拆開封裝的話,只會把問題搞得更麻煩而已。 + std::string overrideValue = (mgrPrefs.useSCPCTypingMode) + ? "" + : _userOverrideModel->suggest(_walkedNodes, [self getBuilderCursorIndex], + [[NSDate date] timeIntervalSince1970]); + + if (!overrideValue.empty()) + { + NSInteger cursorIndex = [self _actualCandidateCursorIndex]; + std::vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); + double highestScore = FindHighestScore(nodes, kEpsilon); + _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, + static_cast(highestScore)); + } +} + +- (void)setBuilderCursorIndex:(NSInteger)value +{ + _builder->setCursorIndex(value); +} + +- (NSInteger)getBuilderCursorIndex +{ + return _builder->cursorIndex(); +} + +- (NSInteger)getBuilderLength +{ + return _builder->length(); +} + +- (void)deleteBuilderReadingInFrontOfCursor +{ + _builder->deleteReadingBeforeCursor(); +} + +- (void)deleteBuilderReadingAfterCursor +{ + _builder->deleteReadingAfterCursor(); +} + +- (NSArray *)getCandidatesArray +{ + NSMutableArray *candidatesArray = [[NSMutableArray alloc] init]; + + 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 + stable_sort(nodes.begin(), nodes.end(), NodeAnchorDescendingSorter()); + + // then use the C++ trick to retrieve the candidates for each node at/crossing the cursor + for (std::vector::iterator ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) + { + const std::vector &candidates = (*ni).node->candidates(); + for (std::vector::const_iterator ci = candidates.begin(), ce = candidates.end(); + ci != ce; ++ci) + [candidatesArray addObject:[NSString stringWithUTF8String:(*ci).value.c_str()]]; + } + return candidatesArray; +} + +#pragma mark - 威注音認為有必要單獨拿出來處理的部分,交給 Swift 則有些困難。 + +- (BOOL)isPrintable:(UniChar)charCode +{ + return isprint(charCode); +} + @end diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift new file mode 100644 index 00000000..5f9857ae --- /dev/null +++ b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift @@ -0,0 +1,329 @@ +// 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() + 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 = getCurrentMandarinParser() + + 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_HandleInput.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift new file mode 100644 index 00000000..71cdea47 --- /dev/null +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -0,0 +1,426 @@ +// 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 Input with States. + +@objc extension KeyHandler { + 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 into variable. + let inputText: String = input.inputText ?? "" + let emptyState = InputState.Empty() + + // Ignore the input if its inputText is empty. + // Reason: such inputs may be functional key combinations. + + if (inputText).isEmpty { + return false + } + + // Ignore the input if the composing buffer is empty with no reading + // and there is some function key combination. + let isFunctionKey: Bool = + input.isControlHotKey || (input.isCommandHold || input.isOptionHotKey || input.isNumericPad) + if !(state is InputState.NotEmpty) && !(state is InputState.AssociatedPhrases) && isFunctionKey { + return false + } + + // MARK: 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. + clear() + stateCallback(emptyState) + + // When shift is pressed, don't do further processing... + // ...since it outputs capital letter anyway. + if input.isShiftHold { + return false + } + + // If ASCII but not printable, don't use insertText:replacementRange: + // Certain apps don't handle non-ASCII char insertions. + if charCode < 0x80 && !isPrintable(charCode) { + return false + } + + // Commit the entire input buffer. + let committingState = InputState.Committing(poppedText: inputText.lowercased()) + stateCallback(committingState) + stateCallback(emptyState) + + return true + } + + // MARK: Numeric Pad Processing. + if input.isNumericPad { + if !input.isLeft && !input.isRight && !input.isDown + && !input.isUp && !input.isSpace && isPrintable(charCode) + { + clear() + stateCallback(emptyState) + let committing = InputState.Committing(poppedText: inputText.lowercased()) + stateCallback(committing) + stateCallback(emptyState) + return true + } + } + + // MARK: Handle Candidates. + if state is InputState.ChoosingCandidate { + return _handleCandidateState( + state, input: input, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Handle Associated Phrases. + if state is InputState.AssociatedPhrases { + let result = _handleCandidateState( + state, input: input, stateCallback: stateCallback, errorCallback: errorCallback) + if result { + return true + } else { + stateCallback(emptyState) + } + } + + // MARK: Handle Marking. + if state is InputState.Marking { + let marking = state as! InputState.Marking + + if _handleMarkingState( + state as! InputState.Marking, input: input, stateCallback: stateCallback, + errorCallback: errorCallback) + { + return true + } + + state = marking.convertToInputting() + stateCallback(state) + } + + // MARK: Handle BPMF Keys. + var composeReading: Bool = false + let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold + + // See if Phonetic reading is valid. + if !skipPhoneticHandling && chkKeyValidity(charCode) { + 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 = checkWhetherToneMarkerConfirmsPhoneticReadingBuffer() + if !composeReading { + let inputting = buildInputtingState() + stateCallback(inputting) + return true + } + + } + + // See if we have composition if Enter/Space is hit and buffer is not empty. + // We use "|=" conditioning so that the tone marker key is also taken into account. + // However, Swift does not support "|=". + composeReading = composeReading || (!isPhoneticReadingBufferEmpty() && (input.isSpace || input.isEnter)) + if composeReading { + let reading = getSyllableCompositionFromPhoneticReadingBuffer() + + if !ifLangModelHasUnigrams(forKey: reading) { + IME.prtDebugIntel("B49C0979") + errorCallback() + let inputting = buildInputtingState() + stateCallback(inputting) + return true + } + + // ... and insert it into the lattice grid... + insertReadingToBuilder(atCursor: reading) + + // ... then walk the lattice grid... + let poppedText = _popOverflowComposingTextAndWalk() + + // ... get and tweak override model suggestion if possible... + dealWithOverrideModelSuggestions() + + // ... then update the text. + clearPhoneticReadingBuffer() + + let inputting = buildInputtingState() + inputting.poppedText = poppedText + stateCallback(inputting) + + if mgrPrefs.useSCPCTypingMode { + let choosingCandidates: InputState.ChoosingCandidate = _buildCandidateState( + inputting, + useVerticalMode: input.useVerticalMode) + if choosingCandidates.candidates.count == 1 { + clear() + let text: String = choosingCandidates.candidates.first ?? "" + let committing = InputState.Committing(poppedText: text) + stateCallback(committing) + + if !mgrPrefs.associatedPhrasesEnabled { + stateCallback(emptyState) + } else { + let associatedPhrases = + buildAssociatePhraseState( + withKey: text, + useVerticalMode: input.useVerticalMode) as? InputState.AssociatedPhrases + if let associatedPhrases = associatedPhrases { + stateCallback(associatedPhrases) + } else { + stateCallback(emptyState) + } + } + } else { + stateCallback(choosingCandidates) + } + } + return true + } + + // MARK: Calling candidate window using Space or Down or PageUp / PageDn. + + if isPhoneticReadingBufferEmpty() && (state is InputState.NotEmpty) + && (input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse || input.isSpace + || input.isPageDown || input.isPageUp || input.isTab + || (input.useVerticalMode && (input.isVerticalModeOnlyChooseCandidateKey))) + { + if input.isSpace { + // 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 + if (composingBuffer.count) != 0 { + let committing = InputState.Committing(poppedText: composingBuffer) + stateCallback(committing) + } + clear() + let committing = InputState.Committing(poppedText: " ") + stateCallback(committing) + let empty = InputState.Empty() + stateCallback(empty) + } else if ifLangModelHasUnigrams(forKey: " ") { + insertReadingToBuilder(atCursor: " ") + let poppedText = _popOverflowComposingTextAndWalk() + let inputting = buildInputtingState() + inputting.poppedText = poppedText + stateCallback(inputting) + } + return true + } + } + let choosingCandidates = _buildCandidateState( + state as! InputState.NotEmpty, + useVerticalMode: input.useVerticalMode) + stateCallback(choosingCandidates) + return true + } + + // MARK: - + + // MARK: Esc + if input.isESC { return _handleEscWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) } + + // 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 && input.isControlHold) + ? _handleCtrlCommandEnterWithState(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() + 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 = getCurrentMandarinParser() + 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. + // 砍掉這一段會導致「F1-F12 按鍵干擾組字區」的問題。 + // 暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。 + if (state is InputState.NotEmpty) || !isPhoneticReadingBufferEmpty() { + IME.prtDebugIntel( + "Blocked data: charCode: \(charCode), keyCode: \(input.keyCode)") + IME.prtDebugIntel("A9BFF20E") + errorCallback() + stateCallback(state) + return true + } + + return false + } +} diff --git a/Source/Modules/ControllerModules/KeyHandler_Misc.swift b/Source/Modules/ControllerModules/KeyHandler_Misc.swift new file mode 100644 index 00000000..a28d5219 --- /dev/null +++ b/Source/Modules/ControllerModules/KeyHandler_Misc.swift @@ -0,0 +1,48 @@ +// 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 getCurrentMandarinParser() -> String { + return (mgrPrefs.mandarinParserName + "_") + } + + 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 new file mode 100644 index 00000000..df970152 --- /dev/null +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -0,0 +1,535 @@ +// 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: - § State managements. +@objc extension KeyHandler { + + // MARK: - 構築狀態(State Building) + func buildInputtingState() -> InputState.Inputting { + // 觸發資料封裝更新,否則下文拿到的數據會是錯的。 + packageBufferStateMaterials() + // 獲取封裝好的資料 + let composedText = getComposedText() + let packagedCursorIndex = UInt(getPackagedCursorIndex()) + let resultOfBefore = getStrLocationResult(isAfter: false) + let resultOfAfter = getStrLocationResult(isAfter: true) + + // 初期化狀態 + let newState = InputState.Inputting(composingBuffer: composedText, cursorIndex: packagedCursorIndex) + + // 組建提示文本 + var tooltip = "" + if (resultOfBefore == "") && (resultOfAfter != "") { + tooltip = String(format: NSLocalizedString("Cursor is after \"%@\".", comment: ""), resultOfAfter) + } + if (resultOfBefore != "") && (resultOfAfter == "") { + tooltip = String(format: NSLocalizedString("Cursor is before \"%@\".", comment: ""), resultOfBefore) + } + if (resultOfBefore != "") && (resultOfAfter != "") { + tooltip = String( + format: NSLocalizedString("Cursor is between \"%@\" and \"%@\".", comment: ""), + resultOfAfter, resultOfBefore) + } + + // 給新狀態安插配置好的提示文本、且送出新狀態 + newState.tooltip = tooltip + return newState + } + + // MARK: - 用以生成候選詞數組 + func _buildCandidateState( + _ currentState: InputState.NotEmpty, + useVerticalMode: Bool + ) -> InputState.ChoosingCandidate { + let candidatesArray = getCandidatesArray() + + let state = InputState.ChoosingCandidate( + composingBuffer: currentState.composingBuffer, + cursorIndex: currentState.cursorIndex, + candidates: candidatesArray, + useVerticalMode: useVerticalMode) + return state + } + + // MARK: - 用以處理就地新增自訂語彙時的行為 + func _handleMarkingState( + _ state: InputState.Marking, + input: keyParser, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + + if input.isESC { + let inputting = buildInputtingState() + stateCallback(inputting) + return true + } + + // Enter + if input.isEnter { + if let keyHandlerDelegate = delegate { + if !keyHandlerDelegate.keyHandler(self, didRequestWriteUserPhraseWith: state) { + IME.prtDebugIntel("5B69CC8D") + errorCallback() + return true + } + } + + let inputting = buildInputtingState() + stateCallback(inputting) + return true + } + + // Shift + Left + if (input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward) && (input.isShiftHold) { + var index = state.markerIndex + if index > 0 { + index = UInt((state.composingBuffer as NSString).previousUtf16Position(for: Int(index))) + let marking = InputState.Marking( + composingBuffer: state.composingBuffer, + cursorIndex: state.cursorIndex, + markerIndex: index, + readings: state.readings) + marking.tooltipForInputting = state.tooltipForInputting + + if marking.markedRange.length == 0 { + let inputting = marking.convertToInputting() + stateCallback(inputting) + } else { + stateCallback(marking) + } + } else { + IME.prtDebugIntel("1149908D") + errorCallback() + stateCallback(state) + } + return true + } + + // Shift + Right + if (input.isCursorForward || input.emacsKey == vChewingEmacsKey.forward) && (input.isShiftHold) { + var index = state.markerIndex + // 這裡繼續用 NSString 是為了與 Zonble 之前引入的 NSStringUtils 相容。 + // 不然的話,這行判斷會失敗、引發「9B51408D」錯誤。 + if index < ((state.composingBuffer as NSString).length) { + index = UInt((state.composingBuffer as NSString).nextUtf16Position(for: Int(index))) + let marking = InputState.Marking( + composingBuffer: state.composingBuffer, + cursorIndex: state.cursorIndex, + markerIndex: index, + readings: state.readings) + marking.tooltipForInputting = state.tooltipForInputting + if marking.markedRange.length == 0 { + let inputting = marking.convertToInputting() + stateCallback(inputting) + } else { + stateCallback(marking) + } + } else { + IME.prtDebugIntel("9B51408D") + errorCallback() + stateCallback(state) + } + return true + } + 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() + 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 _handleCtrlCommandEnterWithState( + _ 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() + 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() + 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() + 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() + 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() + 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() + stateCallback(inputting) + } else { + IME.prtDebugIntel("7045E6F3") + errorCallback() + stateCallback(state) + } + } + return true + } +} diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 764f3804..d3cea4a8 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -60,7 +60,7 @@ class ctlInputMethod: IMKInputController { // 所以才需要「currentKeyHandler」這個假 keyHandler。 // 這個「currentKeyHandler」僅用來讓其他模組知道當前的輸入模式是什麼模式,除此之外別無屌用。 static var currentKeyHandler: KeyHandler = KeyHandler() - @objc static var currentInputMode = "" + @objc static var currentInputMode = mgrPrefs.mostRecentInputMode // MARK: - Keyboard Layout Specifier @@ -75,6 +75,9 @@ class ctlInputMethod: IMKInputController { override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) { super.init(server: server, delegate: delegate, client: inputClient) keyHandler.delegate = self + // 下述兩行很有必要,否則輸入法會在手動重啟之後無法立刻生效。 + activateServer(inputClient) + resetKeyHandler() } // MARK: - KeyHandler Reset Command @@ -95,7 +98,7 @@ class ctlInputMethod: IMKInputController { currentClient = client keyHandler.clear() - keyHandler.syncWithPreferences() + keyHandler.ensurePhoneticParser() if let bundleCheckID = (client as? IMKTextInput)?.bundleIdentifier() { if bundleCheckID != Bundle.main.bundleIdentifier { // Override the keyboard layout to the basic one. @@ -598,9 +601,7 @@ extension ctlInputMethod: ctlCandidateDelegate { let selectedValue = state.candidates[Int(index)] keyHandler.fixNode(value: selectedValue) - guard let inputting = keyHandler.buildInputtingState() as? InputState.Inputting else { - return - } + let inputting = keyHandler.buildInputtingState() if mgrPrefs.useSCPCTypingMode { keyHandler.clear() diff --git a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift index bdd05ea4..8226b562 100644 --- a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift +++ b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift @@ -261,6 +261,7 @@ extension ctlInputMethod { } @objc func selfTerminate(_ sender: Any?) { + NSApp.activate(ignoringOtherApps: true) NSApp.terminate(nil) } diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index 9f42bb35..9651963a 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -28,6 +28,7 @@ import Cocoa struct UserDef { static let kIsDebugModeEnabled = "_DebugMode" + static let kMostRecentInputMode = "MostRecentInputMode" static let kUserDataFolderSpecified = "UserDataFolderSpecified" static let kCheckUpdateAutomatically = "CheckUpdateAutomatically" static let kMandarinParser = "MandarinParser" @@ -204,6 +205,7 @@ public class mgrPrefs: NSObject { static var allKeys: [String] { [ UserDef.kIsDebugModeEnabled, + UserDef.kMostRecentInputMode, UserDef.kUserDataFolderSpecified, UserDef.kMandarinParser, UserDef.kBasicKeyboardLayout, @@ -238,6 +240,7 @@ public class mgrPrefs: NSObject { // MARK: - 既然 Preferences Module 的預設屬性不自動寫入 plist,那這邊就先寫入了。 @objc public static func setMissingDefaults() { UserDefaults.standard.setDefault(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled) + UserDefaults.standard.setDefault(mgrPrefs.mostRecentInputMode, forKey: UserDef.kMostRecentInputMode) UserDefaults.standard.setDefault(mgrPrefs.checkUpdateAutomatically, forKey: UserDef.kCheckUpdateAutomatically) UserDefaults.standard.setDefault( mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow) @@ -271,6 +274,9 @@ public class mgrPrefs: NSObject { @UserDefault(key: UserDef.kIsDebugModeEnabled, defaultValue: false) @objc static var isDebugModeEnabled: Bool + @UserDefault(key: UserDef.kMostRecentInputMode, defaultValue: "") + @objc static var mostRecentInputMode: String + @UserDefault(key: UserDef.kCheckUpdateAutomatically, defaultValue: false) @objc static var checkUpdateAutomatically: Bool diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index f8b6a3b7..fc9eea2b 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -13,7 +13,9 @@ 5B27AD6B27CB1F9B000ED75B /* data-zhuyinwen.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B27AD6927CB1F9B000ED75B /* data-zhuyinwen.txt */; }; 5B2DB16F27AF6891006D874E /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B2DB16D27AF6891006D874E /* data-chs.txt */; }; 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 */; }; @@ -29,7 +31,9 @@ 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 */; }; 5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; }; + 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 */; }; @@ -185,7 +189,9 @@ 5B2DB16E27AF6891006D874E /* data-cht.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "data-cht.txt"; path = "Data/data-cht.txt"; sourceTree = ""; }; 5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = ""; }; 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 = ""; }; @@ -206,8 +212,10 @@ 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 = ""; }; 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_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 = ""; }; @@ -324,7 +332,7 @@ D4E33D8927A838CF006DB1CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; D4E33D8E27A838F0006DB1CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; D4E569DA27A34CC100AC2CEF /* KeyHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyHandler.h; sourceTree = ""; }; - D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyHandler.mm; sourceTree = ""; }; + D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyHandler.mm; sourceTree = ""; usesTabs = 0; }; D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveUtil.swift; sourceTree = ""; }; D4F0BBE0279AF8B30071253C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; D4F0BBE2279B08900071253C /* Chronosphere.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Chronosphere.h; sourceTree = ""; }; @@ -423,6 +431,10 @@ D461B791279DAC010070E734 /* InputState.swift */, D4E569DA27A34CC100AC2CEF /* KeyHandler.h */, D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */, + 5B7F225C2808501000DDD3CB /* KeyHandler_HandleInput.swift */, + 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */, + 5B61B0C9280BEFD4002E3CFA /* KeyHandler_Misc.swift */, + 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */, D456576D279E4F7B00DF6BC9 /* KeyParser.swift */, 6ACC3D3E27914F2400F1B140 /* KeyValueBlobReader.cpp */, 6ACC3D3C27914AAB00F1B140 /* KeyValueBlobReader.h */, @@ -1061,6 +1073,7 @@ D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */, 5BA9FD4527FEF3C9002DE248 /* ToolbarItemStyleViewController.swift in Sources */, 5BA9FD4127FEF3C8002DE248 /* PreferencesStyle.swift in Sources */, + 5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */, 5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */, 6ACC3D442793701600F1B140 /* ParselessPhraseDB.cpp in Sources */, D461B792279DAC010070E734 /* InputState.swift in Sources */, @@ -1076,6 +1089,7 @@ 5BE78BE027B38804005EA1BE /* LMConsolidator.mm in Sources */, D456576E279E4F7B00DF6BC9 /* KeyParser.swift in Sources */, 5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */, + 5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */, 5BA9FD4327FEF3C8002DE248 /* Preferences.swift in Sources */, 5BA9FD4427FEF3C8002DE248 /* SegmentedControlStyleViewController.swift in Sources */, D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */, @@ -1090,6 +1104,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 */, @@ -1099,6 +1114,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 */, @@ -1434,7 +1450,6 @@ "$(OTHER_CFLAGS)", "-fcxx-modules", ); - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; @@ -1475,8 +1490,6 @@ "$(OTHER_CFLAGS)", "-fcxx-modules", ); - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Release; };