[WIP] Starts to extract input states from the input controller.

This commit is contained in:
zonble 2022-01-24 02:13:18 +08:00
parent 43d9e8bd42
commit 177cba5d56
4 changed files with 392 additions and 127 deletions

View File

@ -52,6 +52,7 @@
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */; }; D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */; };
D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */ = {isa = PBXBuildFile; productRef = D44FB7492791B829003C80A6 /* VXHanConvert */; }; D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */ = {isa = PBXBuildFile; productRef = D44FB7492791B829003C80A6 /* VXHanConvert */; };
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */; }; D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */; };
D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; };
D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; }; D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; };
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; }; D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; }; D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; };
@ -204,6 +205,7 @@
D44FB7482791B346003C80A6 /* VXHanConvert */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = VXHanConvert; path = Packages/VXHanConvert; sourceTree = "<group>"; }; D44FB7482791B346003C80A6 /* VXHanConvert */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = VXHanConvert; path = Packages/VXHanConvert; sourceTree = "<group>"; };
D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = "<group>"; }; D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = "<group>"; };
D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = "<group>"; }; D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = "<group>"; };
D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = "<group>"; };
D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; }; D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; }; D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; }; D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
@ -299,6 +301,7 @@
6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */, 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */,
D41355D6278D7409005E5CBD /* LanguageModelManager.h */, D41355D6278D7409005E5CBD /* LanguageModelManager.h */,
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */, D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
D461B791279DAC010070E734 /* InputState.swift */,
D47B92BF27972AC800458394 /* main.swift */, D47B92BF27972AC800458394 /* main.swift */,
D427F76B278CA1BA004A2160 /* AppDelegate.swift */, D427F76B278CA1BA004A2160 /* AppDelegate.swift */,
D44FB74427915555003C80A6 /* Preferences.swift */, D44FB74427915555003C80A6 /* Preferences.swift */,
@ -696,6 +699,7 @@
files = ( files = (
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */, D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */,
6ACC3D442793701600F1B140 /* ParselessPhraseDB.cpp in Sources */, 6ACC3D442793701600F1B140 /* ParselessPhraseDB.cpp in Sources */,
D461B792279DAC010070E734 /* InputState.swift in Sources */,
D47B92C027972AD100458394 /* main.swift in Sources */, D47B92C027972AD100458394 /* main.swift in Sources */,
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */, D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */,
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */, D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */,

View File

@ -53,8 +53,11 @@ namespace Formosa {
void setJoinSeparator(const string& separator); void setJoinSeparator(const string& separator);
const string joinSeparator() const; const string joinSeparator() const;
// TODO: Remove these later.
size_t markerCursorIndex() const; size_t markerCursorIndex() const;
// TODO: Remove these later.
void setMarkerCursorIndex(size_t inNewIndex); void setMarkerCursorIndex(size_t inNewIndex);
vector<string> readingsAtRange(size_t begin, size_t end) const; vector<string> readingsAtRange(size_t begin, size_t end) const;
Grid& grid(); Grid& grid();

View File

@ -79,6 +79,12 @@ static inline NSString *LocalizationNotNeeded(NSString *s) {
return s; return s;
} }
@interface McBopomofoInputMethodController()
{
InputState *_state;
}
@end
@interface McBopomofoInputMethodController (VTCandidateController) <VTCandidateControllerDelegate> @interface McBopomofoInputMethodController (VTCandidateController) <VTCandidateControllerDelegate>
@end @end
@ -115,8 +121,6 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
if (_builder) { if (_builder) {
delete _builder; delete _builder;
} }
// the two client pointers are weak pointers (i.e. we don't retain them)
// therefore we don't do anything about it
} }
- (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)client - (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)client
@ -145,6 +149,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
_composingBuffer = [[NSMutableString alloc] init]; _composingBuffer = [[NSMutableString alloc] init];
_inputMode = kBopomofoModeIdentifier; _inputMode = kBopomofoModeIdentifier;
_state = [[InputStateEmpty alloc] init];
} }
return self; return self;
@ -205,9 +210,8 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
// reset the state // reset the state
_currentDeferredClient = nil; _currentDeferredClient = nil;
_currentCandidateClient = nil; _currentCandidateClient = nil;
_builder->clear(); InputStateEmpty *newState = [[InputStateEmpty alloc] init];
_walkedNodes.clear(); [self handleState:newState client:client];
[_composingBuffer setString:@""];
// checks and populates the default settings // checks and populates the default settings
switch (Preferences.keyboardLayout) { switch (Preferences.keyboardLayout) {
@ -237,27 +241,13 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
_languageModel->setExternalConverterEnabled(Preferences.chineseConversionStyle == 1); _languageModel->setExternalConverterEnabled(Preferences.chineseConversionStyle == 1);
[(AppDelegate *)[NSApp delegate] checkForUpdate]; [(AppDelegate *)[NSApp delegate] checkForUpdate];
} }
- (void)deactivateServer:(id)client - (void)deactivateServer:(id)client
{ {
// clean up reading buffer residues InputStateDeactive *newState = [[InputStateDeactive alloc] init];
if (!_bpmfReadingBuffer->isEmpty()) { [self handleState:newState client:client];
_bpmfReadingBuffer->clear();
[client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
}
// commit any residue in the composing buffer
[self commitComposition:client];
_currentDeferredClient = nil;
_currentCandidateClient = nil;
gCurrentCandidateController.delegate = nil;
gCurrentCandidateController.visible = NO;
[_candidates removeAllObjects];
[self _hideTooltip];
} }
- (void)setValue:(id)value forTag:(long)tag client:(id)sender - (void)setValue:(id)value forTag:(long)tag client:(id)sender
@ -288,6 +278,13 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
_inputMode = newInputMode; _inputMode = newInputMode;
_languageModel = newLanguageModel; _languageModel = newLanguageModel;
if (_builder) {
delete _builder;
_builder = new BlockReadingBuilder(_languageModel);
_builder->setJoinSeparator("-");
}
if (!_bpmfReadingBuffer->isEmpty()) { if (!_bpmfReadingBuffer->isEmpty()) {
_bpmfReadingBuffer->clear(); _bpmfReadingBuffer->clear();
[self updateClientComposingBuffer:sender]; [self updateClientComposingBuffer:sender];
@ -297,11 +294,6 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
[self commitComposition:sender]; [self commitComposition:sender];
} }
if (_builder) {
delete _builder;
_builder = new BlockReadingBuilder(_languageModel);
_builder->setJoinSeparator("-");
}
} }
} }
@ -335,22 +327,34 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
return; return;
} }
NSString *buffer = @"";
if ([_state isKindOfClass: [InputStateInputting class]] ) {
buffer = [(InputStateInputting *)_state composingBuffer];
}
// Chinese conversion. // Chinese conversion.
NSString *buffer = [self _convertToSimplifiedChineseIfRequired:_composingBuffer]; // NSString *buffer = [self _convertToSimplifiedChineseIfRequired:_composingBuffer];
// commit the text, clear the state // commit the text, clear the state
[client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; if ([buffer length]) {
_builder->clear(); buffer = [self _convertToSimplifiedChineseIfRequired:_composingBuffer];
_walkedNodes.clear(); [client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
[_composingBuffer setString:@""]; }
gCurrentCandidateController.visible = NO;
[_candidates removeAllObjects]; // zonble: these should be handled by states.
[self _hideTooltip]; // _builder->clear();
// _walkedNodes.clear();
// [_composingBuffer setString:@""];
// gCurrentCandidateController.visible = NO;
// [_candidates removeAllObjects];
// [self _hideTooltip];
} }
NS_INLINE size_t min(size_t a, size_t b) { return a < b ? a : b; } NS_INLINE size_t min(size_t a, size_t b) { return a < b ? a : b; }
NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
// TODO: bug #28 is more likely to live in this method. // TODO: bug #28 is more likely to live in this method.
- (void)updateClientComposingBuffer:(id)client - (void)updateClientComposingBuffer:(id)client
{ {
@ -480,10 +484,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if (_builder->grid().width() > (size_t)composingBufferSize) { if (_builder->grid().width() > (size_t)composingBufferSize) {
if (_walkedNodes.size() > 0) { if (_walkedNodes.size() > 0) {
NodeAnchor &anchor = _walkedNodes[0]; NodeAnchor &anchor = _walkedNodes[0];
NSString *popedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()]; NSString *poppedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()];
// Chinese conversion. // Chinese conversion.
popedText = [self _convertToSimplifiedChineseIfRequired:popedText]; poppedText = [self _convertToSimplifiedChineseIfRequired:poppedText];
[client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; [client insertText:poppedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
_builder->removeHeadReadings(anchor.spanningLength); _builder->removeHeadReadings(anchor.spanningLength);
} }
} }
@ -542,9 +546,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
} }
// if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it
if (![_composingBuffer length] && BOOL isFunctionKey = ((flags & NSEventModifierFlagCommand) || (flags & NSEventModifierFlagControl) || (flags & NSEventModifierFlagOption) || (flags & NSEventModifierFlagNumericPad));
_bpmfReadingBuffer->isEmpty() && if (![_state isKindOfClass:[InputStateInputting class]] && isFunctionKey) {
((flags & NSEventModifierFlagCommand) || (flags & NSEventModifierFlagControl) || (flags & NSEventModifierFlagOption) || (flags & NSEventModifierFlagNumericPad))) {
return NO; return NO;
} }
@ -554,9 +557,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
} }
else if (flags & NSAlphaShiftKeyMask) { else if (flags & NSAlphaShiftKeyMask) {
// process all possible combination, we hope. // process all possible combination, we hope.
if ([_composingBuffer length]) { InputStateEmpty *emptyState = [[InputStateEmpty alloc] init];
[self commitComposition:client]; [self handleState:emptyState client:client];
}
// first commit everything in the buffer. // first commit everything in the buffer.
if (flags & NSEventModifierFlagShift) { if (flags & NSEventModifierFlagShift) {
@ -569,75 +571,79 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
} }
// when shift is pressed, don't do further processing, since it outputs capital letter anyway. // when shift is pressed, don't do further processing, since it outputs capital letter anyway.
NSString *popedText = [inputText lowercaseString]; InputStateCommitting *committingState = [[InputStateCommitting alloc] initWithPoppedText:[inputText lowercaseString]];
[client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; [self handleState:committingState client:client];
[self handleState:emptyState client:client];
return YES; return YES;
} }
if (flags & NSEventModifierFlagNumericPad) { if (flags & NSEventModifierFlagNumericPad) {
if (keyCode != kLeftKeyCode && keyCode != kRightKeyCode && keyCode != kDownKeyCode && keyCode != kUpKeyCode && charCode != 32 && isprint(charCode)) { if (keyCode != kLeftKeyCode && keyCode != kRightKeyCode && keyCode != kDownKeyCode && keyCode != kUpKeyCode && charCode != 32 && isprint(charCode)) {
if ([_composingBuffer length]) { InputStateEmpty *emptyState = [[InputStateEmpty alloc] init];
[self commitComposition:client]; [self handleState:emptyState client:client];
} InputStateCommitting *commiting = [[InputStateCommitting alloc] initWithPoppedText:[inputText lowercaseString]];
[self handleState:commiting client:client];
NSString *popedText = [inputText lowercaseString]; [self handleState:emptyState client:client];
[client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
return YES; return YES;
} }
} }
// if we have candidate, it means we need to pass the event to the candidate handler // if we have candidate, it means we need to pass the event to the candidate handler
if ([_candidates count]) { if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) {
return [self _handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode emacsKey:(McBopomofoEmacsKey)emacsKey]; return [self _handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode emacsKey:(McBopomofoEmacsKey)emacsKey];
} }
// If we have marker index. if ([_state isKindOfClass:[InputStateMarking class]]) {
if (_builder->markerCursorIndex() != SIZE_MAX) { InputStateMarking *currentState = (InputStateMarking *)_state;
// ESC
if (charCode == 27) { if (charCode == 27) {
_builder->setMarkerCursorIndex(SIZE_MAX); _builder->setMarkerCursorIndex(SIZE_MAX);
[self updateClientComposingBuffer:client]; InputStateInputting *state = [self buildInputingState];
[self handleState:state client:client];
return YES; return YES;
} }
// Enter // Enter
if (charCode == 13) { if (charCode == 13) {
if ([self _writeUserPhrase]) { if (![self _writeUserPhrase]) {
_builder->setMarkerCursorIndex(SIZE_MAX);
}
else {
[self beep]; [self beep];
} }
[self updateClientComposingBuffer:client]; InputStateInputting *state = [self buildInputingState];
[self handleState:state client:client];
return YES; return YES;
} }
// Shift + left // Shift + left
if ((keyCode == cursorBackwardKey || emacsKey == McBopomofoEmacsKeyBackward) if ((keyCode == cursorBackwardKey || emacsKey == McBopomofoEmacsKeyBackward)
&& (flags & NSEventModifierFlagShift)) { && (flags & NSEventModifierFlagShift)) {
if (_builder->markerCursorIndex() > 0) { NSUInteger index = currentState.markerIndex;
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1); if (index > 0) {
index -= 1;
InputStateMarking *state = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:index];
[self handleState:state client:client];
} }
else { else {
[self handleState:currentState client:client];
[self beep]; [self beep];
} }
[self updateClientComposingBuffer:client];
return YES; return YES;
} }
// Shift + Right // Shift + Right
if ((keyCode == cursorForwardKey || emacsKey == McBopomofoEmacsKeyForward) if ((keyCode == cursorForwardKey || emacsKey == McBopomofoEmacsKeyForward)
&& (flags & NSEventModifierFlagShift)) { && (flags & NSEventModifierFlagShift)) {
if (_builder->markerCursorIndex() < _builder->length()) { NSUInteger index = currentState.markerIndex;
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1); if (index < currentState.composingBuffer.length) {
index -= 1;
InputStateMarking *state = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:index];
[self handleState:state client:client];
} }
else { else {
[self handleState:currentState client:client];
[self beep]; [self beep];
} }
[self updateClientComposingBuffer:client];
return YES; return YES;
} }
_builder->setMarkerCursorIndex(SIZE_MAX);
} }
// see if it's valid BPMF reading // see if it's valid BPMF reading
if (_bpmfReadingBuffer->isValidKey((char)charCode)) { if (_bpmfReadingBuffer->isValidKey((char)charCode)) {
_bpmfReadingBuffer->combineKey((char)charCode); _bpmfReadingBuffer->combineKey((char)charCode);
@ -647,7 +653,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
// update the composing buffer // update the composing buffer
composeReading = _bpmfReadingBuffer->hasToneMarker(); composeReading = _bpmfReadingBuffer->hasToneMarker();
if (!composeReading) { if (!composeReading) {
[self updateClientComposingBuffer:client]; [self handleState:_state client:client];
return YES; return YES;
} }
} }
@ -662,7 +668,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
// see if we have a unigram for this // see if we have a unigram for this
if (!_languageModel->hasUnigramsForKey(reading)) { if (!_languageModel->hasUnigramsForKey(reading)) {
[self beep]; [self beep];
[self updateClientComposingBuffer:client]; [self handleState:_state client:client];
return YES; return YES;
} }
@ -670,7 +676,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
_builder->insertReadingAtCursor(reading); _builder->insertReadingAtCursor(reading);
// then walk the lattice // then walk the lattice
[self popOverflowComposingTextAndWalk:client]; NSString *poppedText = [self popOverflowComposingTextAndWalk];
// get user override model suggestion // get user override model suggestion
string overrideValue = (_inputMode == kPlainBopomofoModeIdentifier) ? "" : string overrideValue = (_inputMode == kPlainBopomofoModeIdentifier) ? "" :
@ -685,7 +691,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
// then update the text // then update the text
_bpmfReadingBuffer->clear(); _bpmfReadingBuffer->clear();
[self updateClientComposingBuffer:client];
InputStateInputting *state = [self buildInputingState];
state.poppedText = poppedText;
[self handleState:state client:client];
if (_inputMode == kPlainBopomofoModeIdentifier) { if (_inputMode == kPlainBopomofoModeIdentifier) {
[self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client];
@ -701,14 +710,18 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
// if the spacebar is NOT set to be a selection key // if the spacebar is NOT set to be a selection key
if ((flags & NSEventModifierFlagShift) != 0 || !Preferences.chooseCandidateUsingSpace) { if ((flags & NSEventModifierFlagShift) != 0 || !Preferences.chooseCandidateUsingSpace) {
if (_builder->cursorIndex() >= _builder->length()) { if (_builder->cursorIndex() >= _builder->length()) {
[_composingBuffer appendString:@" "];
[self commitComposition:client];
_bpmfReadingBuffer->clear(); _bpmfReadingBuffer->clear();
InputStateCommitting *commiting = [[InputStateCommitting alloc] initWithPoppedText:@" "];
[self handleState:commiting client:client];
InputStateEmpty *empty = [[InputStateEmpty alloc] init];
[self handleState:empty client:client];
} }
else if (_languageModel->hasUnigramsForKey(" ")) { else if (_languageModel->hasUnigramsForKey(" ")) {
_builder->insertReadingAtCursor(" "); _builder->insertReadingAtCursor(" ");
[self popOverflowComposingTextAndWalk:client]; NSString *poppedText = [self popOverflowComposingTextAndWalk];
[self updateClientComposingBuffer:client]; InputStateInputting *state = [self buildInputingState];
state.poppedText = poppedText;
[self handleState:state client:client];
} }
return YES; return YES;
@ -725,13 +738,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if (escToClearInputBufferEnabled) { if (escToClearInputBufferEnabled) {
// if the optioon is enabled, we clear everythiong including the composing // if the optioon is enabled, we clear everythiong including the composing
// buffer, walked nodes and the reading. // buffer, walked nodes and the reading.
if (![_composingBuffer length]) { InputStateEmpty *empty = [[InputStateEmpty alloc] init];
return NO; [self handleState:empty client:client];
}
_bpmfReadingBuffer->clear();
_builder->clear();
_walkedNodes.clear();
[_composingBuffer setString:@""];
} }
else { else {
// if reading is not empty, we cancel the reading; Apple's built-in // if reading is not empty, we cancel the reading; Apple's built-in
@ -742,8 +750,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if (_bpmfReadingBuffer->isEmpty()) { if (_bpmfReadingBuffer->isEmpty()) {
// no nee to beep since the event is deliberately triggered by user // no nee to beep since the event is deliberately triggered by user
if (![_state isKindOfClass:[InputStateInputting class]]) {
if (![_composingBuffer length]) {
return NO; return NO;
} }
} }
@ -752,7 +759,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
} }
} }
[self updateClientComposingBuffer:client]; InputStateInputting *newState = [self buildInputingState];
[self handleState:newState client:client];
return YES; return YES;
} }
@ -762,7 +770,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self beep]; [self beep];
} }
else { else {
if (![_composingBuffer length]) { if (![_state isKindOfClass:[InputStateInputting class]]) {
return NO; return NO;
} }
@ -784,7 +792,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
} }
} }
[self updateClientComposingBuffer:client]; InputStateInputting *newState = [self buildInputingState];
[self handleState:newState client:client];
return YES; return YES;
} }
@ -794,7 +803,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self beep]; [self beep];
} }
else { else {
if (![_composingBuffer length]) { if (![_state isKindOfClass:[InputStateInputting class]]) {
return NO; return NO;
} }
@ -815,7 +824,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
} }
} }
[self updateClientComposingBuffer:client]; InputStateInputting *newState = [self buildInputingState];
[self handleState:newState client:client];
return YES; return YES;
} }
@ -824,7 +834,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self beep]; [self beep];
} }
else { else {
if (![_composingBuffer length]) { if (![_state isKindOfClass:[InputStateInputting class]]) {
return NO; return NO;
} }
@ -836,7 +846,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
} }
} }
[self updateClientComposingBuffer:client]; InputStateInputting *newState = [self buildInputingState];
[self handleState:newState client:client];
return YES; return YES;
} }
@ -845,7 +856,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self beep]; [self beep];
} }
else { else {
if (![_composingBuffer length]) { if (![_state isKindOfClass:[InputStateInputting class]]) {
return NO; return NO;
} }
@ -857,7 +868,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
} }
} }
[self updateClientComposingBuffer:client]; InputStateInputting *newState = [self buildInputingState];
[self handleState:newState client:client];
return YES; return YES;
} }
@ -865,14 +877,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if (!_bpmfReadingBuffer->isEmpty()) { if (!_bpmfReadingBuffer->isEmpty()) {
[self beep]; [self beep];
} }
[self updateClientComposingBuffer:client]; InputStateInputting *newState = [self buildInputingState];
[self handleState:newState client:client];
return YES; return YES;
} }
// Backspace // Backspace
if (charCode == 8) { if (charCode == 8) {
if (_bpmfReadingBuffer->isEmpty()) { if (_bpmfReadingBuffer->isEmpty()) {
if (![_composingBuffer length]) { if (![_state isKindOfClass:[InputStateInputting class]]) {
return NO; return NO;
} }
@ -888,14 +901,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
_bpmfReadingBuffer->backspace(); _bpmfReadingBuffer->backspace();
} }
[self updateClientComposingBuffer:client]; InputStateInputting *newState = [self buildInputingState];
[self handleState:newState client:client];
return YES; return YES;
} }
// Delete // Delete
if (keyCode == kDeleteKeyCode || emacsKey == McBopomofoEmacsKeyDelete) { if (keyCode == kDeleteKeyCode || emacsKey == McBopomofoEmacsKeyDelete) {
if (_bpmfReadingBuffer->isEmpty()) { if (_bpmfReadingBuffer->isEmpty()) {
if (![_composingBuffer length]) { if (![_state isKindOfClass:[InputStateInputting class]]) {
return NO; return NO;
} }
@ -911,32 +925,32 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self beep]; [self beep];
} }
[self updateClientComposingBuffer:client]; InputStateInputting *newState = [self buildInputingState];
[self handleState:newState client:client];
return YES; return YES;
} }
// Enter // Enter
if (charCode == 13) { if (charCode == 13) {
if (![_composingBuffer length]) { InputStateInputting *newState = [self buildInputingState];
return NO; [self handleState:newState client:client];
if ([_state isKindOfClass:[InputStateInputting class]]) {
InputStateInputting *current = (InputStateInputting *)_state;
NSString *composingBuffer = current.composingBuffer;
InputStateCommitting *commiting = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer];
[self handleState:commiting client:client];
InputState *empty = [[InputState alloc] init];
[self handleState:empty client:client];
return YES;
} }
[self commitComposition:client]; return NO;
return YES;
} }
// punctuation list // punctuation list
if ((char)charCode == '`') { if ((char)charCode == '`') {
if (_languageModel->hasUnigramsForKey(string("_punctuation_list"))) { if ([self _handlePunctuation:string("_punctuation_list") usingVerticalMode:useVerticalMode client:client]) {
if (_bpmfReadingBuffer->isEmpty()) {
_builder->insertReadingAtCursor(string("_punctuation_list"));
[self popOverflowComposingTextAndWalk:client];
[self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client];
}
else { // If there is still unfinished bpmf reading, ignore the punctuation
[self beep];
}
[self updateClientComposingBuffer:client];
return YES; return YES;
} }
} }
@ -956,11 +970,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
} }
if ((char)charCode >= 'A' && (char)charCode <= 'Z') { if ((char)charCode >= 'A' && (char)charCode <= 'Z') {
if ([_composingBuffer length]) { string letter = string("_letter_") + string(1, (char)charCode);
string letter = string("_letter_") + string(1, (char)charCode); if ([self _handlePunctuation:letter usingVerticalMode:useVerticalMode client:client]) {
if ([self _handlePunctuation:letter usingVerticalMode:useVerticalMode client:client]) { return YES;
return YES;
}
} }
} }
@ -969,7 +981,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
// actually consumed) // actually consumed)
if ([_composingBuffer length] || !_bpmfReadingBuffer->isEmpty()) { if ([_composingBuffer length] || !_bpmfReadingBuffer->isEmpty()) {
[self beep]; [self beep];
[self updateClientComposingBuffer:client]; [self handleState:_state client:client];
return YES; return YES;
} }
@ -979,19 +991,23 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (BOOL)_handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client - (BOOL)_handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client
{ {
if (_languageModel->hasUnigramsForKey(customPunctuation)) { if (_languageModel->hasUnigramsForKey(customPunctuation)) {
NSString *poppedText = @"";
if (_bpmfReadingBuffer->isEmpty()) { if (_bpmfReadingBuffer->isEmpty()) {
_builder->insertReadingAtCursor(customPunctuation); _builder->insertReadingAtCursor(customPunctuation);
[self popOverflowComposingTextAndWalk:client]; poppedText = [self popOverflowComposingTextAndWalk];
} }
else { // If there is still unfinished bpmf reading, ignore the punctuation else { // If there is still unfinished bpmf reading, ignore the punctuation
[self beep]; [self beep];
} }
[self updateClientComposingBuffer:client]; InputStateInputting *inputting = [self buildInputingState];
inputting.poppedText = poppedText;
[self handleState:inputting client:client];
if (_inputMode == kPlainBopomofoModeIdentifier && _bpmfReadingBuffer->isEmpty()) { if (_inputMode == kPlainBopomofoModeIdentifier && _bpmfReadingBuffer->isEmpty()) {
[self collectCandidates]; [self collectCandidates];
if ([_candidates count] == 1) { if ([_candidates count] == 1) {
[self commitComposition:client]; InputStateEmpty *empty = [[InputStateEmpty alloc] init];
[self handleState:empty client:client];
} }
else { else {
[self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client];
@ -1228,6 +1244,186 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
return [self handleInputText:inputText key:keyCode modifiers:flags client:client]; return [self handleInputText:inputText key:keyCode modifiers:flags client:client];
} }
#pragma mark - States
- (InputStateInputting *)buildInputingState
{
// "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();
// 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
for (vector<NodeAnchor>::iterator wi = _walkedNodes.begin(), we = _walkedNodes.end() ; wi != we ; ++wi) {
if ((*wi).node) {
string nodeStr = (*wi).node->currentKeyValue().value;
vector<string> codepoints = OVUTF8Helper::SplitStringByCodePoint(nodeStr);
size_t codepointCount = codepoints.size();
NSString *valueString = [NSString stringWithUTF8String:nodeStr.c_str()];
[composingBuffer appendString:valueString];
// this re-aligns the cursor index in the composed string
// (the actual cursor on the screen) with the builder's logical
// cursor (reading) cursor; each built node has a "spanning length"
// (e.g. two reading blocks has a spanning length of 2), and we
// accumulate those lengthes to calculate the displayed cursor
// index
size_t spanningLength = (*wi).spanningLength;
if (readingCursorIndex + spanningLength <= builderCursorIndex) {
composedStringCursorIndex += [valueString length];
readingCursorIndex += spanningLength;
}
else {
for (size_t i = 0; i < codepointCount && readingCursorIndex < builderCursorIndex; i++) {
composedStringCursorIndex += [[NSString stringWithUTF8String:codepoints[i].c_str()] length];
readingCursorIndex++;
}
}
}
}
// now we gather all the info, we separate the composing buffer to two parts, head and tail,
// 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 *tail = [composingBuffer substringFromIndex:composedStringCursorIndex];
NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]];
NSInteger cursorIndex = composedStringCursorIndex + [reading length];
InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText cursorIndex:cursorIndex];
return newState;
}
- (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
// lose their influence over the whole MLE anyway -- so tht when
// the user type along, the already composed text at front will
// be popped out
NSString *poppedText = @"";
NSInteger composingBufferSize = Preferences.composingBufferSize;
if (_builder->grid().width() > (size_t)composingBufferSize) {
if (_walkedNodes.size() > 0) {
NodeAnchor &anchor = _walkedNodes[0];
poppedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()];
// Chinese conversion.
poppedText = [self _convertToSimplifiedChineseIfRequired:poppedText];
_builder->removeHeadReadings(anchor.spanningLength);
}
}
[self walk];
return poppedText;
}
- (void)commitCurrentTextIfRequired:(id)client
{
// if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper)
// then we defer the update in the next runloop round -- so that the composing buffer is not
// meaninglessly flushed, an annoying bug in Terminal.app since Mac OS X 10.5
// if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) {
// if (_currentDeferredClient) {
// [self performSelector:@selector(updateClientComposingBuffer:) withObject:_currentDeferredClient afterDelay:0.0];
// }
// return;
// }
// TODO: handle this later
if ([_state isKindOfClass: [InputStateInputting class]] ) {
NSString *buffer = [(InputStateInputting *)_state composingBuffer];
buffer = [self _convertToSimplifiedChineseIfRequired:_composingBuffer];
[client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
}
}
- (void)handleState:(InputState *)newState client:(id)client
{
if ([newState isKindOfClass:[InputStateDeactive class]]) {
// clean up reading buffer residues
if (!_bpmfReadingBuffer->isEmpty()) {
_bpmfReadingBuffer->clear();
[client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
}
// commit any residue in the composing buffer
[self commitCurrentTextIfRequired:client];
_currentDeferredClient = nil;
_currentCandidateClient = nil;
gCurrentCandidateController.delegate = nil;
gCurrentCandidateController.visible = NO;
// [_candidates removeAllObjects];
[self _hideTooltip];
_state = newState;
return;
}
if ([newState isKindOfClass:[InputStateEmpty class]]) {
[self commitCurrentTextIfRequired:client];
_builder->clear();
_walkedNodes.clear();
// [_composingBuffer setString:@""];
gCurrentCandidateController.visible = NO;
// [_candidates removeAllObjects];
[self _hideTooltip];
_state = newState;
return;
}
if ([newState isKindOfClass:[InputStateCommitting class]]) {
NSString *poppedText = [(InputStateCommitting *)newState poppedText];
[client insertText:poppedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
_state = newState;
return;
}
if ([newState isKindOfClass:[InputStateInputting class]]) {
InputStateInputting *current = (InputStateInputting *)newState;
NSString *poppedText = current.poppedText;
if (poppedText.length) {
[client insertText:poppedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
}
gCurrentCandidateController.visible = NO;
[self _hideTooltip];
NSString *composedText = [(InputStateInputting *)newState composingBuffer];
NSUInteger cursorIndex = [(InputStateInputting *)newState cursorIndex];
// we must use NSAttributedString so that the cursor is visible --
// can't just use NSString
NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
NSMarkedClauseSegmentAttributeName: @0};
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText attributes:attrDict];
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
// i.e. the client app needs to take care of where to put ths composing buffer
[client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
// _latestReadingCursor = cursorIndex;
_state = newState;
return;
}
}
#pragma mark - Private methods #pragma mark - Private methods
+ (VTHorizontalCandidateController *)horizontalCandidateController + (VTHorizontalCandidateController *)horizontalCandidateController
@ -1353,7 +1549,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0);
NSInteger cursor = _latestReadingCursor; NSInteger cursor = 0;
// NSInteger cursor = _latestReadingCursor;
if ([_state respondsToSelector:@selector(cursorIndex)]) {
cursor = [[_state performSelector:@selector(cursorIndex)] integerValue];
}
if (cursor == [_composingBuffer length] && cursor != 0) { if (cursor == [_composingBuffer length] && cursor != 0) {
cursor--; cursor--;
} }
@ -1477,7 +1679,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
{ {
NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0);
NSInteger cursor = _latestReadingCursor; // NSInteger cursor = _latestReadingCursor;
NSInteger cursor = 0;
if ([_state respondsToSelector:@selector(cursorIndex)]) {
cursor = [[_state performSelector:@selector(cursorIndex)] integerValue];
}
// zonble: should be handled with state.
if (cursor == [_composingBuffer length] && cursor != 0) { if (cursor == [_composingBuffer length] && cursor != 0) {
cursor--; cursor--;
} }

51
Source/InputState.swift Normal file
View File

@ -0,0 +1,51 @@
import Foundation
class InputState: NSObject {
}
class InputStateDeactive: InputState {
}
class InputStateEmpty: InputState {
}
class InputStateCommitting: InputState {
@objc private(set) var poppedText: String = ""
@objc convenience init(poppedText: String) {
self.init()
self.poppedText = poppedText
}
}
class InputStateInputting: InputState {
@objc private(set) var composingBuffer: String = ""
@objc private(set) var cursorIndex: UInt = 0
@objc var poppedText: String = ""
@objc init(composingBuffer: String, cursorIndex: UInt) {
self.composingBuffer = composingBuffer
self.cursorIndex = cursorIndex
}
}
class InputStateMarking: InputStateInputting {
@objc private(set) var markerIndex: UInt = 0
@objc private(set) var markedRange: NSRange = NSRange(location: 0, length: 0)
@objc var tooltip: String {
return ""
}
@objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt) {
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
self.markerIndex = markerIndex
let begin = min(cursorIndex, markerIndex)
let end = max(cursorIndex, markerIndex)
self.markedRange = NSMakeRange(Int(begin), Int( end - begin))
}
}
class InputStateChoosingCandidate: InputStateInputting {
var markingRang: NSRange = NSRange(location: 0, length: 0)
var candidates: [String] = []
}