From 4c358c1c1dff43e6a2768f4839b76d26f92a5def Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 03:37:40 +0800 Subject: [PATCH 01/15] Converts InputMethodController to Swift. --- McBopomofo.xcodeproj/project.pbxproj | 12 +- Source/InputMethodController.h | 32 -- Source/InputMethodController.mm | 705 ------------------------- Source/InputMethodController.swift | 546 +++++++++++++++++++ Source/InputState.swift | 333 ++++++------ Source/KeyHandler.h | 14 +- Source/KeyHandler.mm | 46 +- Source/LanguageModelManager+Privates.h | 13 + Source/LanguageModelManager.h | 7 +- Source/LanguageModelManager.mm | 11 + Source/McBopomofo-Bridging-Header.h | 8 +- 11 files changed, 781 insertions(+), 946 deletions(-) delete mode 100644 Source/InputMethodController.h delete mode 100644 Source/InputMethodController.mm create mode 100644 Source/InputMethodController.swift create mode 100644 Source/LanguageModelManager+Privates.h diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index c4ae5ad4..2b04991d 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; - 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */; }; 6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; }; 6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; }; 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; }; @@ -59,6 +58,7 @@ D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; }; D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3B82796A8A000657FF3 /* PreferencesTests.swift */; }; D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */; }; + D4A13D5A27A59F0B003BE359 /* InputMethodController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A13D5927A59D5C003BE359 /* InputMethodController.swift */; }; D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */; }; D4E569DF27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DE27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm */; }; D4E569E027A4123200AC2CEF /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */; }; @@ -101,8 +101,6 @@ 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 6A0D4EA915FC0D2D00ABF4B3 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 6A0D4EAB15FC0D2D00ABF4B3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputMethodController.h; sourceTree = ""; }; - 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = InputMethodController.mm; sourceTree = ""; }; 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Bopomofo.tiff; sourceTree = ""; }; 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "Bopomofo@2x.tiff"; sourceTree = ""; }; 6A0D4EF515FC0DA600ABF4B3 /* McBopomofo-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "McBopomofo-Info.plist"; sourceTree = ""; }; @@ -221,6 +219,8 @@ D485D3B62796A8A000657FF3 /* McBopomofoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = McBopomofoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D485D3B82796A8A000657FF3 /* PreferencesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesTests.swift; sourceTree = ""; }; D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateTests.swift; sourceTree = ""; }; + D495583A27A5C6C4006ADE1C /* LanguageModelManager+Privates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LanguageModelManager+Privates.h"; sourceTree = ""; }; + D4A13D5927A59D5C003BE359 /* InputMethodController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputMethodController.swift; 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 = ""; }; D4E569DD27A40F1300AC2CEF /* McBopomofoTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofoTests-Bridging-Header.h"; sourceTree = ""; }; @@ -308,9 +308,9 @@ 6A0D4F1215FC0EB100ABF4B3 /* Engine */, 6ACA41E715FC1D9000935EF6 /* Installer */, 6A0D4F4715FC0EB900ABF4B3 /* Resources */, - 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */, - 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */, + D4A13D5927A59D5C003BE359 /* InputMethodController.swift */, D41355D6278D7409005E5CBD /* LanguageModelManager.h */, + D495583A27A5C6C4006ADE1C /* LanguageModelManager+Privates.h */, D41355D7278D7409005E5CBD /* LanguageModelManager.mm */, D4E569DA27A34CC100AC2CEF /* KeyHandler.h */, D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */, @@ -720,12 +720,12 @@ D461B792279DAC010070E734 /* InputState.swift in Sources */, D47B92C027972AD100458394 /* main.swift in Sources */, D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */, + D4A13D5A27A59F0B003BE359 /* InputMethodController.swift in Sources */, D44FB74527915565003C80A6 /* Preferences.swift in Sources */, D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */, D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */, D456576E279E4F7B00DF6BC9 /* KeyHandlerInput.swift in Sources */, D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */, - 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */, D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h deleted file mode 100644 index 11055f70..00000000 --- a/Source/InputMethodController.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2011 and onwards The McBopomofo Authors. -// -// 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: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// 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 -#import -#import "McBopomofo-Swift.h" - -@interface McBopomofoInputMethodController : IMKInputController - -- (void)handleState:(InputState *)newState client:(id)client; - -@end diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm deleted file mode 100644 index 80ae1d80..00000000 --- a/Source/InputMethodController.mm +++ /dev/null @@ -1,705 +0,0 @@ -// Copyright (c) 2011 and onwards The McBopomofo Authors. -// -// 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: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// 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 "McBopomofoLM.h" -#import "InputMethodController.h" -#import "KeyHandler.h" -#import "LanguageModelManager.h" - -// Swift Packages -@import CandidateUI; -@import NotifierUI; -@import TooltipUI; -@import OpenCCBridge; -@import VXHanConvert; - -//// C++ namespace usages -using namespace std; -using namespace McBopomofo; - -static const NSInteger kMinKeyLabelSize = 10; - -VTCandidateController *gCurrentCandidateController = nil; - -// https://clang-analyzer.llvm.org/faq.html -__attribute__((annotate("returns_localized_nsstring"))) -static inline NSString *LocalizationNotNeeded(NSString *s) { - return s; -} - -@interface McBopomofoInputMethodController () -{ - // the current text input client; we need to keep this when candidate panel is on - id _currentCandidateClient; - - // a special deferred client for Terminal.app fix - id _currentDeferredClient; - - KeyHandler *_keyHandler; - InputState *_state; -} -@end - -@interface McBopomofoInputMethodController (VTCandidateController) -@end - -@interface McBopomofoInputMethodController (KeyHandlerDelegate) -@end - -@interface McBopomofoInputMethodController (UI) -+ (VTHorizontalCandidateController *)horizontalCandidateController; -+ (VTVerticalCandidateController *)verticalCandidateController; -+ (TooltipController *)tooltipController; -- (void)_showTooltip:(NSString *)tooltip composingBuffer:(NSString *)composingBuffer cursorIndex:(NSInteger)cursorIndex client:(id)client; -- (void)_hideTooltip; -@end - -@implementation McBopomofoInputMethodController - -- (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)client -{ - // an instance is initialized whenever a text input client (a Mac app) requires - // text input from an IME - - self = [super initWithServer:server delegate:delegate client:client]; - if (self) { - _keyHandler = [[KeyHandler alloc] init]; - _keyHandler.delegate = self; - _state = [[InputStateEmpty alloc] init]; - } - - return self; -} - -- (NSMenu *)menu -{ - // a menu instance (autoreleased) is requested every time the user click on the input menu - NSMenu *menu = [[NSMenu alloc] initWithTitle:LocalizationNotNeeded(@"Input Method Menu")]; - NSString *inputMode = _keyHandler.inputMode; - - [menu addItemWithTitle:NSLocalizedString(@"McBopomofo Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; - - NSMenuItem *chineseConversionMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"g"]; - chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; - chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; - - NSMenuItem *halfWidthPunctuationMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@"h"]; - halfWidthPunctuationMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; - halfWidthPunctuationMenuItem.state = Preferences.halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff; - - BOOL optionKeyPressed = [[NSEvent class] respondsToSelector:@selector(modifierFlags)] && ([NSEvent modifierFlags] & NSEventModifierFlagOption); - - if (inputMode == kBopomofoModeIdentifier && optionKeyPressed) { - NSMenuItem *phaseReplacementMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Phrase Replacement", @"") action:@selector(togglePhraseReplacementEnabled:) keyEquivalent:@""]; - phaseReplacementMenuItem.state = Preferences.phraseReplacementEnabled ? NSControlStateValueOn : NSControlStateValueOff; - } - - [menu addItem:[NSMenuItem separatorItem]]; - [menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""]; - if (inputMode == kPlainBopomofoModeIdentifier) { - NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesPlainBopomofo:) keyEquivalent:@""]; - [menu addItem:editExcludedPhrasesItem]; - } else { - [menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; - [menu addItemWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesMcBopomofo:) keyEquivalent:@""]; - if (optionKeyPressed) { - [menu addItemWithTitle:NSLocalizedString(@"Edit Phrase Replacement Table", @"") action:@selector(openPhraseReplacementMcBopomofo:) keyEquivalent:@""]; - } - } - [menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; - [menu addItem:[NSMenuItem separatorItem]]; - - [menu addItemWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; - [menu addItemWithTitle:NSLocalizedString(@"About McBopomofo…", @"") action:@selector(showAbout:) keyEquivalent:@""]; - return menu; -} - -#pragma mark - IMKStateSetting protocol methods - -- (void)activateServer:(id)client -{ - [[NSUserDefaults standardUserDefaults] synchronize]; - - // Override the keyboard layout. Use US if not set. - NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; - [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - - // reset the state - _currentDeferredClient = nil; - _currentCandidateClient = nil; - - [_keyHandler clear]; - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - [self handleState:empty client:client]; - - // checks and populates the default settings - [_keyHandler syncWithPreferences]; - [(AppDelegate *) NSApp.delegate checkForUpdate]; -} - -- (void)deactivateServer:(id)client -{ - [_keyHandler clear]; - - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - [self handleState:empty client:client]; - - InputStateDeactivated *inactive = [[InputStateDeactivated alloc] init]; - [self handleState:inactive client:client]; -} - -- (void)setValue:(id)value forTag:(long)tag client:(id)sender -{ - NSString *newInputMode; - - if ([value isKindOfClass:[NSString class]] && [value isEqual:kPlainBopomofoModeIdentifier]) { - newInputMode = kPlainBopomofoModeIdentifier; - } else { - newInputMode = kBopomofoModeIdentifier; - } - - // Only apply the changes if the value is changed - if (![_keyHandler.inputMode isEqualToString:newInputMode]) { - [[NSUserDefaults standardUserDefaults] synchronize]; - - // Remember to override the keyboard layout again -- treat this as an activate event. - NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; - [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - [_keyHandler clear]; - _keyHandler.inputMode = newInputMode; - InputState *empty = [[InputState alloc] init]; - [self handleState:empty client:sender]; - } -} - -#pragma mark - IMKServerInput protocol methods - -- (NSUInteger)recognizedEvents:(id)sender -{ - return NSEventMaskKeyDown | NSEventMaskFlagsChanged; -} - -- (BOOL)handleEvent:(NSEvent *)event client:(id)client -{ - if ([event type] == NSEventMaskFlagsChanged) { - NSString *functionKeyKeyboardLayoutID = Preferences.functionKeyboardLayout; - NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; - - // If no override is needed, just return NO. - if ([functionKeyKeyboardLayoutID isEqualToString:basisKeyboardLayoutID]) { - return NO; - } - - // Function key pressed. - BOOL includeShift = Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey; - if ((event.modifierFlags & ~NSEventModifierFlagShift) || ((event.modifierFlags & NSEventModifierFlagShift) && includeShift)) { - // Override the keyboard layout and let the OS do its thing - [client overrideKeyboardWithKeyboardNamed:functionKeyKeyboardLayoutID]; - return NO; - } - - // Revert to the basis layout when the function key is released - [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - return NO; - } - - NSRect textFrame = NSZeroRect; - NSDictionary *attributes = nil; - BOOL useVerticalMode = NO; - - @try { - attributes = [client attributesForCharacterIndex:0 lineHeightRectangle:&textFrame]; - useVerticalMode = attributes[@"IMKTextOrientation"] && [attributes[@"IMKTextOrientation"] integerValue] == 0; - } - @catch (NSException *e) { - // exception may raise while using Twitter.app's search filed. - } - - if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && [NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) { - // special handling for com.apple.Terminal - _currentDeferredClient = client; - } - - KeyHandlerInput *input = [[KeyHandlerInput alloc] initWithEvent:event isVerticalMode:useVerticalMode]; - BOOL result = [_keyHandler handleInput:input state:_state stateCallback:^(InputState *state) { - [self handleState:state client:client]; - } candidateSelectionCallback:^{ - NSLog(@"candidate window updated."); - } errorCallback:^{ - NSBeep(); - }]; - - return result; -} - - -#pragma mark - States Handling - -- (NSString *)_convertToSimplifiedChineseIfRequired:(NSString *)text -{ - if (!Preferences.chineseConversionEnabled) { - return text; - } - - if (Preferences.chineseConversionStyle == 1) { - return text; - } - - if (Preferences.chineseConversionEngine == 1) { - return [VXHanConvert convertToSimplifiedFrom:text]; - } - - return [OpenCCBridge convertToSimplified:text]; -} - -- (void)_commitText:(NSString *)text client:(id)client -{ - NSString *buffer = [self _convertToSimplifiedChineseIfRequired:text]; - if (!buffer.length) { - return;; - } - - // 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) { - id currentDeferredClient = _currentDeferredClient; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [currentDeferredClient insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - }); - } - return; - } - [client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; -} - -- (void)handleState:(InputState *)newState client:(id)client -{ -// NSLog(@"new state: %@ / current state: %@", newState, _state); - - // We need to set the state to the member variable since the candidate - // window need to read the candidates from it. - InputState *previous = _state; - _state = newState; - - if ([newState isKindOfClass:[InputStateDeactivated class]]) { - [self _handleDeactivated:(InputStateDeactivated *) newState previous:previous client:client]; - } else if ([newState isKindOfClass:[InputStateEmpty class]]) { - [self _handleEmpty:(InputStateEmpty *) newState previous:previous client:client]; - } else if ([newState isKindOfClass:[InputStateEmptyIgnoringPreviousState class]]) { - [self _handleEmptyIgnoringPrevious:(InputStateEmptyIgnoringPreviousState *) newState previous:previous client:client]; - } else if ([newState isKindOfClass:[InputStateCommitting class]]) { - [self _handleCommitting:(InputStateCommitting *) newState previous:previous client:client]; - } else if ([newState isKindOfClass:[InputStateInputting class]]) { - [self _handleInputting:(InputStateInputting *) newState previous:previous client:client]; - } else if ([newState isKindOfClass:[InputStateMarking class]]) { - [self _handleMarking:(InputStateMarking *) newState previous:previous client:client]; - } else if ([newState isKindOfClass:[InputStateChoosingCandidate class]]) { - [self _handleChoosingCandidate:(InputStateChoosingCandidate *) newState previous:previous client:client]; - } -} - -- (void)_handleDeactivated:(InputStateDeactivated *)state previous:(InputState *)previous client:(id)client -{ - // commit any residue in the composing buffer - if ([previous isKindOfClass:[InputStateInputting class]]) { - NSString *buffer = ((InputStateInputting *) previous).composingBuffer; - [self _commitText:buffer client:client]; - } - [client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - - _currentDeferredClient = nil; - _currentCandidateClient = nil; - - gCurrentCandidateController.delegate = nil; - gCurrentCandidateController.visible = NO; - [self _hideTooltip]; -} - -- (void)_handleEmpty:(InputStateEmpty *)state previous:(InputState *)previous client:(id)client -{ - // commit any residue in the composing buffer - if ([previous isKindOfClass:[InputStateInputting class]]) { - NSString *buffer = ((InputStateInputting *) previous).composingBuffer; - [self _commitText:buffer client:client]; - } - - [client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - gCurrentCandidateController.visible = NO; - [self _hideTooltip]; -} - -- (void)_handleEmptyIgnoringPrevious:(InputStateEmptyIgnoringPreviousState *)state previous:(InputState *)previous client:(id)client -{ - [client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - gCurrentCandidateController.visible = NO; - [self _hideTooltip]; -} - -- (void)_handleCommitting:(InputStateCommitting *)state previous:(InputState *)previous client:(id)client -{ - NSString *poppedText = state.poppedText; - [self _commitText:poppedText client:client]; - gCurrentCandidateController.visible = NO; - [self _hideTooltip]; -} - -- (void)_handleInputting:(InputStateInputting *)state previous:(InputState *)previous client:(id)client -{ - NSString *poppedText = state.poppedText; - if (poppedText.length) { - [self _commitText:poppedText client:client]; - } - - NSUInteger cursorIndex = state.cursorIndex; - NSAttributedString *attrString = state.attributedString; - - // 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)]; - - gCurrentCandidateController.visible = NO; - [self _hideTooltip]; -} - -- (void)_handleMarking:(InputStateMarking *)state previous:(InputState *)previous client:(id)client -{ - NSUInteger cursorIndex = state.cursorIndex; - NSAttributedString *attrString = state.attributedString; - - // 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)]; - - gCurrentCandidateController.visible = NO; - if (state.tooltip.length) { - [self _showTooltip:state.tooltip composingBuffer:state.composingBuffer cursorIndex:state.markerIndex client:client]; - } else { - [self _hideTooltip]; - } -} - -- (void)_handleChoosingCandidate:(InputStateChoosingCandidate *)state previous:(InputState *)previous client:(id)client -{ - NSUInteger cursorIndex = state.cursorIndex; - NSAttributedString *attrString = state.attributedString; - - // 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)]; - - if (![previous isKindOfClass:[InputStateChoosingCandidate class]]) { - [self _showCandidateWindowWithState:state client:client]; - } -} - -- (void)_showCandidateWindowWithState:(InputStateChoosingCandidate *)state client:(id)client -{ - // set the candidate panel style - BOOL useVerticalMode = state.useVerticalMode; - - if (useVerticalMode) { - gCurrentCandidateController = [McBopomofoInputMethodController verticalCandidateController]; - } else if (Preferences.useHorizontalCandidateList) { - gCurrentCandidateController = [McBopomofoInputMethodController horizontalCandidateController]; - } else { - gCurrentCandidateController = [McBopomofoInputMethodController verticalCandidateController]; - } - - // set the attributes for the candidate panel (which uses NSAttributedString) - NSInteger textSize = Preferences.candidateListTextSize; - - NSInteger keyLabelSize = textSize / 2; - if (keyLabelSize < kMinKeyLabelSize) { - keyLabelSize = kMinKeyLabelSize; - } - - NSString *ctFontName = Preferences.candidateTextFontName; - NSString *klFontName = Preferences.candidateKeyLabelFontName; - NSString *candidateKeys = Preferences.candidateKeys; - - gCurrentCandidateController.keyLabelFont = klFontName ? [NSFont fontWithName:klFontName size:keyLabelSize] : [NSFont systemFontOfSize:keyLabelSize]; - gCurrentCandidateController.candidateFont = ctFontName ? [NSFont fontWithName:ctFontName size:textSize] : [NSFont systemFontOfSize:textSize]; - - NSMutableArray *keyLabels = [@[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"] mutableCopy]; - - if (candidateKeys.length > 1) { - [keyLabels removeAllObjects]; - for (NSUInteger i = 0, c = candidateKeys.length; i < c; i++) { - [keyLabels addObject:[candidateKeys substringWithRange:NSMakeRange(i, 1)]]; - } - } - - gCurrentCandidateController.keyLabels = keyLabels; - gCurrentCandidateController.delegate = self; - [gCurrentCandidateController reloadData]; - _currentCandidateClient = client; - - NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); - NSInteger cursor = state.cursorIndex; - if (cursor == state.composingBuffer.length && cursor != 0) { - cursor--; - } - - // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch - @try { - [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; - } - @catch (NSException *exception) { - NSLog(@"lineHeightRectangle %@", exception); - } - - if (useVerticalMode) { - [gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0]; - } else { - [gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0]; - } - - gCurrentCandidateController.visible = YES; -} - -#pragma mark - Misc menu items - -- (void)showPreferences:(id)sender -{ - // show the preferences panel, and also make the IME app itself the focus - if ([IMKInputController instancesRespondToSelector:@selector(showPreferences:)]) { - [super showPreferences:sender]; - } else { - [(AppDelegate *) NSApp.delegate showPreferences]; - } - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; -} - -- (void)toggleChineseConverter:(id)sender -{ - BOOL enabled = [Preferences toggleChineseConversionEnabled]; - [NotifierController notifyWithMessage:enabled ? NSLocalizedString(@"Chinese conversion on", @"") : NSLocalizedString(@"Chinese conversion off", @"") stay:NO]; -} - -- (void)toggleHalfWidthPunctuation:(id)sender -{ - BOOL enabled = [Preferences toggleHalfWidthPunctuationEnabled]; - [NotifierController notifyWithMessage:enabled ? NSLocalizedString(@"Half-width punctuation on", @"") : NSLocalizedString(@"Half-width punctuation off", @"") stay:NO]; -} - -- (void)togglePhraseReplacementEnabled:(id)sender -{ - BOOL enabled = [Preferences togglePhraseReplacementEnabled]; - McBopomofoLM *lm = [LanguageModelManager languageModelMcBopomofo]; - lm->setPhraseReplacementEnabled(enabled); -} - -- (void)checkForUpdate:(id)sender -{ - [(AppDelegate *) NSApp.delegate checkForUpdateForced:YES]; -} - -- (BOOL)_checkUserFiles -{ - if (![LanguageModelManager checkIfUserLanguageModelFilesExist]) { - NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), [LanguageModelManager dataFolderPath]]; - [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; - return NO; - } - - return YES; -} - -- (void)_openUserFile:(NSString *)path -{ - if (![self _checkUserFiles]) { - return; - } - NSURL *url = [NSURL fileURLWithPath:path]; - [[NSWorkspace sharedWorkspace] openURL:url]; -} - -- (void)openUserPhrases:(id)sender -{ - [self _openUserFile:[LanguageModelManager userPhrasesDataPathMcBopomofo]]; -} - -- (void)openExcludedPhrasesPlainBopomofo:(id)sender -{ - [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathPlainBopomofo]]; -} - -- (void)openExcludedPhrasesMcBopomofo:(id)sender -{ - [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathMcBopomofo]]; -} - -- (void)openPhraseReplacementMcBopomofo:(id)sender -{ - [self _openUserFile:[LanguageModelManager phraseReplacementDataPathMcBopomofo]]; -} - -- (void)reloadUserPhrases:(id)sender -{ - [LanguageModelManager loadUserPhrases]; - [LanguageModelManager loadUserPhraseReplacement]; -} - -- (void)showAbout:(id)sender -{ - [[NSApplication sharedApplication] orderFrontStandardAboutPanel:sender]; - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; -} - -@end - -#pragma mark - - -@implementation McBopomofoInputMethodController (VTCandidateController) - -- (NSUInteger)candidateCountForController:(VTCandidateController *)controller -{ - if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) { - InputStateChoosingCandidate *state = (InputStateChoosingCandidate *) _state; - return state.candidates.count; - } - return 0; -} - -- (NSString *)candidateController:(VTCandidateController *)controller candidateAtIndex:(NSUInteger)index -{ - if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) { - InputStateChoosingCandidate *state = (InputStateChoosingCandidate *) _state; - return state.candidates[index]; - } - return @""; -} - -- (void)candidateController:(VTCandidateController *)controller didSelectCandidateAtIndex:(NSUInteger)index -{ - gCurrentCandidateController.visible = NO; - - if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) { - InputStateChoosingCandidate *state = (InputStateChoosingCandidate *) _state; - - // candidate selected, override the node with selection - string selectedValue = [state.candidates[index] UTF8String]; - [_keyHandler fixNodeWithValue:selectedValue]; - InputStateInputting *inputting = [_keyHandler _buildInputtingState]; - - if (_keyHandler.inputMode == kPlainBopomofoModeIdentifier) { - [_keyHandler clear]; - InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:inputting.composingBuffer]; - [self handleState:committing client:_currentCandidateClient]; - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - [self handleState:empty client:_currentCandidateClient]; - } else { - [self handleState:inputting client:_currentCandidateClient]; - } - } -} - -@end - -@implementation McBopomofoInputMethodController (KeyHandlerDelegate) - -- (nonnull VTCandidateController *)candidateControllerForKeyHandler:(nonnull KeyHandler *)keyHandler -{ - return gCurrentCandidateController; -} - -- (BOOL)keyHandler:(nonnull KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(nonnull InputStateMarking *)state -{ - if (!state.validToWrite) { - return NO; - } - NSString *userPhrase = state.userPhrase; - [LanguageModelManager writeUserPhrase:userPhrase]; - return YES; -} - -- (void)keyHandler:(nonnull KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index candidateController:(nonnull VTCandidateController *)controller -{ - [self candidateController:gCurrentCandidateController didSelectCandidateAtIndex:index]; -} - -@end - - -@implementation McBopomofoInputMethodController (UI) - -+ (VTHorizontalCandidateController *)horizontalCandidateController -{ - static VTHorizontalCandidateController *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[VTHorizontalCandidateController alloc] init]; - }); - return instance; -} - -+ (VTVerticalCandidateController *)verticalCandidateController -{ - static VTVerticalCandidateController *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[VTVerticalCandidateController alloc] init]; - }); - return instance; -} - -+ (TooltipController *)tooltipController -{ - static TooltipController *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[TooltipController alloc] init]; - }); - return instance; -} - -- (void)_showTooltip:(NSString *)tooltip composingBuffer:(NSString *)composingBuffer cursorIndex:(NSInteger)cursorIndex client:(id)client -{ - NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); - - NSUInteger cursor = (NSUInteger) cursorIndex; - if (cursor == composingBuffer.length && cursor != 0) { - cursor--; - } - - // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch - @try { - [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; - } - @catch (NSException *exception) { - NSLog(@"%@", exception); - } - - [[McBopomofoInputMethodController tooltipController] showTooltip:tooltip atPoint:lineHeightRect.origin]; -} - -- (void)_hideTooltip -{ - if ([McBopomofoInputMethodController tooltipController].window.isVisible) { - [[McBopomofoInputMethodController tooltipController] hide]; - } -} - -@end diff --git a/Source/InputMethodController.swift b/Source/InputMethodController.swift new file mode 100644 index 00000000..5663529d --- /dev/null +++ b/Source/InputMethodController.swift @@ -0,0 +1,546 @@ +// Copyright (c) 2022 and onwards The McBopomofo Authors. +// +// 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: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 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 +import InputMethodKit +import CandidateUI +import NotifierUI +import TooltipUI +import VXHanConvert +import OpenCCBridge + +extension Bool { + var state: NSControl.StateValue { + self ? .on : .off + } +} + +private let kMinKeyLabelSize: CGFloat = 10 + +private var gCurrentCandidateController: CandidateController? + +@objc(McBopomofoInputMethodController) +class McBopomofoInputMethodController: IMKInputController { + + private static let horizontalCandidateController = HorizontalCandidateController() + private static let verticalCandidateController = VerticalCandidateController() + private static let tooltipController = TooltipController() + + // MARK: - + + private var currentCandidateClient: Any? + private var currentDeferredClient: Any? + + private var keyHandler: KeyHandler = KeyHandler() + private var state: InputState = InputState.Empty() + + // MARK: - IMKInputController methods + + override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) { + super.init(server: server, delegate: delegate, client: inputClient) + keyHandler.delegate = self + } + + override func menu() -> NSMenu! { + let menu = NSMenu(title: "Input Method Menu") + menu.addItem(withTitle: NSLocalizedString("McBopomofo Preferences", comment: ""), action: #selector(showPreferences(_:)), keyEquivalent: "") + + let chineseConversionItem = menu.addItem(withTitle: NSLocalizedString("Chinese Conversion", comment: ""), action: #selector(toggleChineseConverter(_:)), keyEquivalent: "g") + chineseConversionItem.keyEquivalentModifierMask = [.command, .control] + chineseConversionItem.state = Preferences.chineseConversionEnabled.state + + let halfWidthPunctuationItem = menu.addItem(withTitle: NSLocalizedString("Use Half-Width Punctuations", comment: ""), action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "h") + halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control] + halfWidthPunctuationItem.state = Preferences.chineseConversionEnabled.state + + let inputMode = keyHandler.inputMode + let optionKeyPressed = NSEvent.modifierFlags.contains(.option) + + if inputMode == kBopomofoModeIdentifier && optionKeyPressed { + let phaseReplacementItem = menu.addItem(withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), action: #selector(togglePhraseReplacementEnabled(_:)), keyEquivalent: "") + phaseReplacementItem.state = Preferences.phraseReplacementEnabled.state + } + + menu.addItem(NSMenuItem.separator()) + menu.addItem(withTitle: NSLocalizedString("User Phrases", comment: ""), action: nil, keyEquivalent: "") + + if inputMode == kPlainBopomofoModeIdentifier { + menu.addItem(withTitle: NSLocalizedString("Edit Excluded Phrases", comment: ""), action: #selector(openExcludedPhrasesPlainBopomofo(_:)), keyEquivalent: "") + } else { + menu.addItem(withTitle: NSLocalizedString("Edit User Phrases", comment: ""), action: #selector(openUserPhrases(_:)), keyEquivalent: "") + menu.addItem(withTitle: NSLocalizedString("Edit Excluded Phrases", comment: ""), action: #selector(openExcludedPhrasesMcBopomofo(_:)), keyEquivalent: "") + if optionKeyPressed { + menu.addItem(withTitle: NSLocalizedString("Edit Phrase Replacement Table", comment: ""), action: #selector(openPhraseReplacementMcBopomofo(_:)), keyEquivalent: "") + } + } + + menu.addItem(withTitle: NSLocalizedString("Reload User Phrases", comment: ""), action: #selector(reloadUserPhrases(_:)), keyEquivalent: "") + menu.addItem(NSMenuItem.separator()) + + menu.addItem(withTitle: NSLocalizedString("Check for Updates…", comment: ""), action: #selector(checkForUpdate(_:)), keyEquivalent: "") + menu.addItem(withTitle: NSLocalizedString("About McBopomofo…", comment: ""), action: #selector(showAbout(_:)), keyEquivalent: "") + return menu + } + + // MARK: - IMKStateSetting protocol methods + + override func activateServer(_ client: Any!) { + UserDefaults.standard.synchronize() + + // Override the keyboard layout. Use US if not set. + (client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: Preferences.basisKeyboardLayout) + // reset the state + currentDeferredClient = nil + currentCandidateClient = nil + + keyHandler.clear() + keyHandler.syncWithPreferences() + self.handle(state: .Empty(), client: client) + (NSApp.delegate as? AppDelegate)?.checkForUpdate() + } + + override func deactivateServer(_ sender: Any!) { + keyHandler.clear() + self.handle(state: .Empty(), client: client) + self.handle(state: .Deactivated(), client: client) + } + + override func setValue(_ value: Any!, forTag tag: Int, client: Any!) { + let newInputMode = value as? String ?? kBopomofoModeIdentifier + if keyHandler.inputMode != newInputMode { + UserDefaults.standard.synchronize() + // Remember to override the keyboard layout again -- treat this as an activate event. + (client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: Preferences.basisKeyboardLayout) + keyHandler.clear() + keyHandler.inputMode = newInputMode + self.handle(state: .Empty(), client: client) + } + } + + // MARK: - - IMKServerInput protocol methods + + override func recognizedEvents(_ sender: Any!) -> Int { + let events: NSEvent.EventTypeMask = [.keyDown, .flagsChanged] + return Int(events.rawValue) + } + + override func handle(_ event: NSEvent!, client: Any!) -> Bool { + if event.type == .flagsChanged { + let functionKeyKeyboardLayoutID = Preferences.functionKeyboardLayout + let basisKeyboardLayoutID = Preferences.basisKeyboardLayout + + if functionKeyKeyboardLayoutID == basisKeyboardLayoutID { + return false + } + + let includeShift = Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey + let notShift = NSEvent.ModifierFlags(rawValue: ~(NSEvent.ModifierFlags.shift.rawValue)) + if event.modifierFlags.contains(notShift) || + (event.modifierFlags.contains(.shift) && includeShift) { + (client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: functionKeyKeyboardLayoutID) + return false + } + (client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: basisKeyboardLayoutID) + return false + } + + var textFrame = NSRect.zero + let attributes: [AnyHashable: Any]? = (client as? IMKTextInput)?.attributes(forCharacterIndex: 0, lineHeightRectangle: &textFrame) + let useVerticalMode = (attributes?["IMKTextOrientation"] as? NSNumber)?.intValue == 0 || false + + if (client as? IMKTextInput)?.bundleIdentifier() == "com.apple.Terminal" && + NSStringFromClass(client.self as! AnyClass) == "IPMDServerClientWrapper" { + currentDeferredClient = client + } + + let input = KeyHandlerInput(event: event, isVerticalMode: useVerticalMode) + let result = keyHandler.handle(input, state: state) { newState in + self.handle(state: newState, client: client) + } candidateSelectionCallback: { + } errorCallback: { + NSSound.beep() + } + return result + } + + // MARK: - Menu Items + + @objc override func showPreferences(_ sender: Any?) { + super.showPreferences(sender) + } + + @objc func toggleChineseConverter(_ sender: Any?) { + let enabled = Preferences.toggleChineseConversionEnabled() + NotifierController.notify(message: enabled ? NSLocalizedString("Chinese conversion on", comment: "") : NSLocalizedString("Chinese conversion off", comment: "")) + } + + @objc func toggleHalfWidthPunctuation(_ sender: Any?) { + let enabled = Preferences.togglePhraseReplacementEnabled() + NotifierController.notify(message: enabled ? NSLocalizedString("Half-width punctuation on", comment: "") : NSLocalizedString("Half-width punctuation off", comment: "")) + } + + @objc func togglePhraseReplacementEnabled(_ sender: Any?) { + let enabled = Preferences.togglePhraseReplacementEnabled() + LanguageModelManager.phraseReplacementEnabled = enabled + } + + @objc func checkForUpdate(_ sender: Any?) { + (NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true) + } + + private func open(userFileAt path: String) { + func checkUserFiles() -> Bool { + if !LanguageModelManager.checkIfUserLanguageModelFilesExist() { + let content = String(format: NSLocalizedString("Please check the permission of at \"%@\".", comment: ""), LanguageModelManager.dataFolderPath) + NonModalAlertWindowController.shared.show(title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) + return false + } + return true + } + + if !checkUserFiles() { + return + } + let url = URL(fileURLWithPath: path) + NSWorkspace.shared.open(url) + } + + @objc func openUserPhrases(_ sender: Any?) { + open(userFileAt: LanguageModelManager.userPhrasesDataPathMcBopomofo) + } + + @objc func openExcludedPhrasesPlainBopomofo(_ sender: Any?) { + open(userFileAt: LanguageModelManager.excludedPhrasesDataPathPlainBopomofo) + } + + @objc func openExcludedPhrasesMcBopomofo(_ sender: Any?) { + open(userFileAt: LanguageModelManager.excludedPhrasesDataPathMcBopomofo) + } + + @objc func openPhraseReplacementMcBopomofo(_ sender: Any?) { + open(userFileAt: LanguageModelManager.phraseReplacementDataPathMcBopomofo) + } + + @objc func reloadUserPhrases(_ sender: Any?) { + LanguageModelManager.loadUserPhrases() + LanguageModelManager.loadUserPhraseReplacement() + } + + @objc func showAbout(_ sender: Any?) { + NSApp.orderFrontStandardAboutPanel(sender) + NSApp.activate(ignoringOtherApps: true) + } + +} + +// MARK: - State Handling + +extension McBopomofoInputMethodController { + + private func handle(state newState: InputState, client: Any?) { + let previous = state + state = newState + + if let newState = newState as? InputState.Deactivated { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.Empty { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.EmptyIgnoringPreviousState { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.Committing { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.Inputting { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.Marking { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.ChoosingCandidate { + handle(state: newState, previous: previous, client: client) + } + } + + private func commit(text: String, client: Any!) { + + func convertToSimplifiedChineseIfRequired(_ text: String) -> String { + if !Preferences.chineseConversionEnabled { + return text + } + if Preferences.chineseConversionStyle == 1 { + return text + } + return Preferences.chineseConversionEngine == 1 ? VXHanConvert.convertToSimplified(from: text) : OpenCCBridge.convertToSimplified(text) ?? "" + } + + let buffer = convertToSimplifiedChineseIfRequired(text) + if buffer.isEmpty { + return + } + // 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 as? IMKTextInput)?.bundleIdentifier() == "com.apple.Terminal" && NSStringFromClass(client.self as! AnyClass) != "IPMDServerClientWrapper" { + let currentDeferredClient = currentDeferredClient + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { + (currentDeferredClient as? IMKTextInput)?.insertText(buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) + } + } + (client as? IMKTextInput)?.insertText(buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) + } + + private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) { + currentDeferredClient = nil + currentCandidateClient = nil + + gCurrentCandidateController?.delegate = nil + gCurrentCandidateController?.visible = false + hideTooltip() + + if let previous = previous as? InputState.NotEmpty { + commit(text: previous.composingBuffer, client: client) + } + (client as? IMKTextInput)?.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + } + + private func handle(state: InputState.Empty, previous: InputState, client: Any?) { + gCurrentCandidateController?.visible = false + hideTooltip() + + guard let client = client as? IMKTextInput else { + return + } + + if let previous = previous as? InputState.NotEmpty { + commit(text: previous.composingBuffer, client: client) + } + client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + } + + private func handle(state: InputState.EmptyIgnoringPreviousState, previous: InputState, client: Any!) { + gCurrentCandidateController?.visible = false + hideTooltip() + + guard let client = client as? IMKTextInput else { + return + } + + client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + } + + private func handle(state: InputState.Committing, previous: InputState, client: Any?) { + gCurrentCandidateController?.visible = false + hideTooltip() + + guard let client = client as? IMKTextInput else { + return + } + + let poppedText = state.poppedText + if !poppedText.isEmpty { + commit(text: poppedText, client: client) + } + client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + } + + private func handle(state: InputState.Inputting, previous: InputState, client: Any?) { + gCurrentCandidateController?.visible = false + hideTooltip() + + guard let client = client as? IMKTextInput else { + return + } + + let poppedText = state.poppedText + if !poppedText.isEmpty { + commit(text: poppedText, client: client) + } + + // 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 this composing buffer + client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + } + + private func handle(state: InputState.Marking, previous: InputState, client: Any?) { + gCurrentCandidateController?.visible = false + guard let client = client as? IMKTextInput else { + hideTooltip() + return + } + + // 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 this composing buffer + client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + + if state.tooltip.isEmpty { + hideTooltip() + } else { + show(tooltip: state.tooltip, composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, client: client) + } + } + + private func handle(state: InputState.ChoosingCandidate, previous: InputState, client: Any?) { + hideTooltip() + guard let client = client as? IMKTextInput else { + gCurrentCandidateController?.visible = false + return + } + + // 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 this composing buffer + client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + if previous is InputState.ChoosingCandidate == false { + show(candidateWindowWith: state, client: client) + } + } +} + +// MARK: - + +extension McBopomofoInputMethodController { + + private func show(candidateWindowWith state: InputState.ChoosingCandidate, client: Any!) { + if state.useVerticalMode { + gCurrentCandidateController = McBopomofoInputMethodController.verticalCandidateController + } else if Preferences.useHorizontalCandidateList { + gCurrentCandidateController = McBopomofoInputMethodController.horizontalCandidateController + } else { + gCurrentCandidateController = McBopomofoInputMethodController.verticalCandidateController + } + + // set the attributes for the candidate panel (which uses NSAttributedString) + let textSize = Preferences.candidateListTextSize + let keyLabelSize = max(textSize / 2, kMinKeyLabelSize) + + func fallbackFont(name: String?, size: CGFloat) -> NSFont { + if let name = name { + return NSFont(name: name, size: size) ?? NSFont.systemFont(ofSize: size) + } + return NSFont.systemFont(ofSize: size) + } + + gCurrentCandidateController?.keyLabelFont = fallbackFont(name: Preferences.candidateKeyLabelFontName, size: keyLabelSize) + gCurrentCandidateController?.candidateFont = fallbackFont(name: Preferences.candidateTextFontName, size: textSize) + + let candidateKeys = Preferences.candidateKeys + let keyLabels = candidateKeys.count > 4 ? Array(candidateKeys) : Array(Preferences.defaultCandidateKeys) + + gCurrentCandidateController?.keyLabels = Array(keyLabels.map { + String($0) + }) + gCurrentCandidateController?.delegate = self + gCurrentCandidateController?.reloadData() + currentCandidateClient = client + + gCurrentCandidateController?.visible = true + + var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0) + var cursor = state.cursorIndex + if cursor == state.composingBuffer.count && cursor != 0 { + cursor -= 1 + } + + (client as? IMKTextInput)?.attributes(forCharacterIndex: Int(cursor), lineHeightRectangle: &lineHeightRect) + + if state.useVerticalMode { + gCurrentCandidateController?.set(windowTopLeftPoint: NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0) + } else { + gCurrentCandidateController?.set(windowTopLeftPoint: NSMakePoint(lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0) + } + } + + private func show(tooltip: String, composingBuffer: String, cursorIndex: UInt, client: Any!) { + var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0) + var cursor = cursorIndex + if cursor == composingBuffer.count && cursor != 0 { + cursor -= 1 + } + (client as? IMKTextInput)?.attributes(forCharacterIndex: Int(cursor), lineHeightRectangle: &lineHeightRect) + McBopomofoInputMethodController.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin) + } + + private func hideTooltip() { + McBopomofoInputMethodController.tooltipController.hide() + } +} + +// MARK: - + +extension McBopomofoInputMethodController: KeyHandlerDelegate { + func candidateController(for keyHandler: KeyHandler) -> Any { + gCurrentCandidateController ?? McBopomofoInputMethodController.verticalCandidateController + } + + func keyHandler(_ keyHandler: KeyHandler, didSelectCandidateAt index: Int, candidateController controller: Any) { + if let controller = controller as? CandidateController { + self.candidateController(controller, didSelectCandidateAtIndex: UInt(index)) + } + } + + func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState) -> Bool { + guard let state = state as? InputState.Marking else { + return false + } + if !state.validToWrite { + return false + } + LanguageModelManager.writeUserPhrase(state.userPhrase) + return true + } +} + +// MARK: - + +extension McBopomofoInputMethodController: CandidateControllerDelegate { + func candidateCountForController(_ controller: CandidateController) -> UInt { + if let state = state as? InputState.ChoosingCandidate { + return UInt(state.candidates.count) + } + return 0 + } + + func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String { + if let state = state as? InputState.ChoosingCandidate { + return state.candidates[Int(index)] + } + return "" + } + + func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) { + gCurrentCandidateController?.visible = false + guard let state = state as? InputState.ChoosingCandidate else { + return + } + let selectedValue = state.candidates[Int(index)] + keyHandler.fixNode(withValue: selectedValue) + + guard let inputting = keyHandler.buildInputtingState() as? InputState.Inputting else { + return + } + + if keyHandler.inputMode == kPlainBopomofoModeIdentifier { + keyHandler.clear() + handle(state: .Committing(poppedText: inputting.composingBuffer), client: currentCandidateClient) + handle(state: .Empty(), client: currentDeferredClient) + } else { + handle(state: inputting, client: currentCandidateClient) + } + } +} diff --git a/Source/InputState.swift b/Source/InputState.swift index 4266f562..cf198bdc 100644 --- a/Source/InputState.swift +++ b/Source/InputState.swift @@ -55,182 +55,191 @@ import Cocoa /// - Choosing Candidate: The candidate window is open to let the user to choose /// one among the candidates. class InputState: NSObject { -} -/// Represents that the input controller is deactivated. -class InputStateDeactivated: InputState { - override var description: String { - "" - } -} - -/// Represents that the composing buffer is empty. -class InputStateEmpty: InputState { - @objc var composingBuffer: String { - "" - } -} - -/// Represents that the composing buffer is empty. -class InputStateEmptyIgnoringPreviousState: InputState { - @objc var composingBuffer: String { - "" - } -} - -/// Represents that the input controller is committing text into client app. -class InputStateCommitting: InputState { - @objc private(set) var poppedText: String = "" - - @objc convenience init(poppedText: String) { - self.init() - self.poppedText = poppedText - } - - override var description: String { - "" - } -} - -/// Represents that the composing buffer is not empty. -class InputStateNotEmpty: InputState { - @objc private(set) var composingBuffer: String = "" - @objc private(set) var cursorIndex: UInt = 0 - - @objc init(composingBuffer: String, cursorIndex: UInt) { - self.composingBuffer = composingBuffer - self.cursorIndex = cursorIndex - } - - override var description: String { - "" - } -} - -/// Represents that the user is inputting text. -class InputStateInputting: InputStateNotEmpty { - @objc var bpmfReading: String = "" - @objc var bpmfReadingCursorIndex: UInt8 = 0 - @objc var poppedText: String = "" - - @objc override init(composingBuffer: String, cursorIndex: UInt) { - super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) - } - - @objc var attributedString: NSAttributedString { - let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ - .underlineStyle: NSUnderlineStyle.single.rawValue, - .markedClauseSegment: 0 - ]) - return attributedSting - } - - override var description: String { - "" - } -} - -private let kMinMarkRangeLength = 2 -private let kMaxMarkRangeLength = 6 - -/// Represents that the user is marking a range in the composing buffer. -class InputStateMarking: InputStateNotEmpty { - @objc private(set) var markerIndex: UInt - @objc private(set) var markedRange: NSRange - @objc var tooltip: String { - - if Preferences.phraseReplacementEnabled { - return NSLocalizedString("Phrase replacement mode is on. Not suggested to add phrase in the mode.", comment: "") + /// Represents that the input controller is deactivated. + @objc (InputStateDeactivated) + class Deactivated: InputState { + override var description: String { + "" } - if Preferences.chineseConversionStyle == 1 && Preferences.chineseConversionEnabled { - return NSLocalizedString("Model based Chinese conversion is on. Not suggested to add phrase in the mode.", comment: "") + } + + /// Represents that the composing buffer is empty. + @objc (InputStateEmpty) + class Empty: InputState { + @objc var composingBuffer: String { + "" } - if markedRange.length == 0 { - return "" + } + + + /// Represents that the composing buffer is empty. + @objc (InputStateEmptyIgnoringPreviousState) + class EmptyIgnoringPreviousState: InputState { + @objc var composingBuffer: String { + "" + } + } + + /// Represents that the input controller is committing text into client app. + @objc (InputStateCommitting) + class Committing: InputState { + @objc private(set) var poppedText: String = "" + + @objc convenience init(poppedText: String) { + self.init() + self.poppedText = poppedText } - let text = (composingBuffer as NSString).substring(with: markedRange) - if markedRange.length < kMinMarkRangeLength { - return String(format: NSLocalizedString("You are now selecting \"%@\". You can add a phrase with two or more characters.", comment: ""), text) - } else if (markedRange.length > kMaxMarkRangeLength) { - return String(format: NSLocalizedString("You are now selecting \"%@\". A phrase cannot be longer than %d characters.", comment: ""), text, kMaxMarkRangeLength) + override var description: String { + "" + } + } + /// Represents that the composing buffer is not empty. + @objc (InputStateNotEmpty) + class NotEmpty: InputState { + @objc private(set) var composingBuffer: String = "" + @objc private(set) var cursorIndex: UInt = 0 + + @objc init(composingBuffer: String, cursorIndex: UInt) { + self.composingBuffer = composingBuffer + self.cursorIndex = cursorIndex + } + + override var description: String { + "" } - return String(format: NSLocalizedString("You are now selecting \"%@\". Press enter to add a new phrase.", comment: ""), text) } - @objc private(set) var readings: [String] = [] + /// Represents that the user is inputting text. + @objc (InputStateInputting) + class Inputting: NotEmpty { + @objc var bpmfReading: String = "" + @objc var bpmfReadingCursorIndex: UInt8 = 0 + @objc var poppedText: String = "" - @objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) { - self.markerIndex = markerIndex - let begin = min(cursorIndex, markerIndex) - let end = max(cursorIndex, markerIndex) - markedRange = NSMakeRange(Int(begin), Int(end - begin)) - self.readings = readings - super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + @objc override init(composingBuffer: String, cursorIndex: UInt) { + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + } + + @objc var attributedString: NSAttributedString { + let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0 + ]) + return attributedSting + } + + override var description: String { + "" + } } - @objc var attributedString: NSAttributedString { - let attributedSting = NSMutableAttributedString(string: composingBuffer) - let end = markedRange.location + markedRange.length + private let kMinMarkRangeLength = 2 + private let kMaxMarkRangeLength = 6 - attributedSting.setAttributes([ - .underlineStyle: NSUnderlineStyle.single.rawValue, - .markedClauseSegment: 0 - ], range: NSRange(location: 0, length: markedRange.location)) - attributedSting.setAttributes([ - .underlineStyle: NSUnderlineStyle.thick.rawValue, - .markedClauseSegment: 1 - ], range: markedRange) - attributedSting.setAttributes([ - .underlineStyle: NSUnderlineStyle.single.rawValue, - .markedClauseSegment: 2 - ], range: NSRange(location: end, - length: composingBuffer.count - end)) - return attributedSting + /// Represents that the user is marking a range in the composing buffer. + @objc (InputStateMarking) + class Marking: NotEmpty { + @objc private(set) var markerIndex: UInt + @objc private(set) var markedRange: NSRange + @objc var tooltip: String { + + if Preferences.phraseReplacementEnabled { + return NSLocalizedString("Phrase replacement mode is on. Not suggested to add phrase in the mode.", comment: "") + } + if Preferences.chineseConversionStyle == 1 && Preferences.chineseConversionEnabled { + return NSLocalizedString("Model based Chinese conversion is on. Not suggested to add phrase in the mode.", comment: "") + } + if markedRange.length == 0 { + return "" + } + + let text = (composingBuffer as NSString).substring(with: markedRange) + if markedRange.length < kMinMarkRangeLength { + return String(format: NSLocalizedString("You are now selecting \"%@\". You can add a phrase with two or more characters.", comment: ""), text) + } else if (markedRange.length > kMaxMarkRangeLength) { + return String(format: NSLocalizedString("You are now selecting \"%@\". A phrase cannot be longer than %d characters.", comment: ""), text, kMaxMarkRangeLength) + } + return String(format: NSLocalizedString("You are now selecting \"%@\". Press enter to add a new phrase.", comment: ""), text) + } + + @objc private(set) var readings: [String] = [] + + @objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) { + self.markerIndex = markerIndex + let begin = min(cursorIndex, markerIndex) + let end = max(cursorIndex, markerIndex) + markedRange = NSMakeRange(Int(begin), Int(end - begin)) + self.readings = readings + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + } + + @objc var attributedString: NSAttributedString { + let attributedSting = NSMutableAttributedString(string: composingBuffer) + let end = markedRange.location + markedRange.length + + attributedSting.setAttributes([ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0 + ], range: NSRange(location: 0, length: markedRange.location)) + attributedSting.setAttributes([ + .underlineStyle: NSUnderlineStyle.thick.rawValue, + .markedClauseSegment: 1 + ], range: markedRange) + attributedSting.setAttributes([ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 2 + ], range: NSRange(location: end, + length: composingBuffer.count - end)) + return attributedSting + } + + override var description: String { + "" + } + + @objc func convertToInputting() -> Inputting { + let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + return state + } + + @objc var validToWrite: Bool { + markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength + } + + @objc var userPhrase: String { + let text = (composingBuffer as NSString).substring(with: markedRange) + let end = markedRange.location + markedRange.length + let readings = readings[markedRange.location.." + /// Represents that the user is choosing in a candidates list. + @objc (InputStateChoosingCandidate) + class ChoosingCandidate: NotEmpty { + @objc private(set) var candidates: [String] = [] + @objc private(set) var useVerticalMode: Bool = false + + @objc init(composingBuffer: String, cursorIndex: UInt, candidates: [String], useVerticalMode: Bool) { + self.candidates = candidates + self.useVerticalMode = useVerticalMode + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + } + + @objc var attributedString: NSAttributedString { + let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0 + ]) + return attributedSting + } + + override var description: String { + "" + } } - @objc func convertToInputting() -> InputStateInputting { - let state = InputStateInputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) - return state - } - - @objc var validToWrite: Bool { - markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength - } - - @objc var userPhrase: String { - let text = (composingBuffer as NSString).substring(with: markedRange) - let end = markedRange.location + markedRange.length - let readings = readings[markedRange.location.." - } } diff --git a/Source/KeyHandler.h b/Source/KeyHandler.h index ac531d6a..b0e14728 100644 --- a/Source/KeyHandler.h +++ b/Source/KeyHandler.h @@ -22,13 +22,9 @@ // OTHER DEALINGS IN THE SOFTWARE. #import -#import -@import CandidateUI; @class KeyHandlerInput; @class InputState; -@class InputStateInputting; -@class InputStateMarking; NS_ASSUME_NONNULL_BEGIN @@ -38,9 +34,9 @@ extern NSString *const kPlainBopomofoModeIdentifier; @class KeyHandler; @protocol KeyHandlerDelegate -- (VTCandidateController *)candidateControllerForKeyHandler:(KeyHandler *)keyHandler; -- (void)keyHandler:(KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index candidateController:(VTCandidateController *)controller; -- (BOOL)keyHandler:(KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(InputStateMarking *)state; +- (id)candidateControllerForKeyHandler:(KeyHandler *)keyHandler; +- (void)keyHandler:(KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index candidateController:(id)controller; +- (BOOL)keyHandler:(KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(InputState *)state; @end @interface KeyHandler : NSObject @@ -52,10 +48,10 @@ candidateSelectionCallback:(void (^)(void))candidateSelectionCallback errorCallback:(void (^)(void))errorCallback; - (void)syncWithPreferences; -- (void)fixNodeWithValue:(std::string)value; +- (void)fixNodeWithValue:(NSString *)value; - (void)clear; -- (InputStateInputting *)_buildInputtingState; +- (InputState *)buildInputtingState; @property (strong, nonatomic) NSString *inputMode; @property (weak, nonatomic) id delegate; diff --git a/Source/KeyHandler.mm b/Source/KeyHandler.mm index efe095ec..be12831d 100644 --- a/Source/KeyHandler.mm +++ b/Source/KeyHandler.mm @@ -25,10 +25,13 @@ #import "Gramambular.h" #import "McBopomofoLM.h" #import "UserOverrideModel.h" -#import "LanguageModelManager.h" +#import "LanguageModelManager+Privates.h" #import "OVUTF8Helper.h" #import "KeyHandler.h" #import "McBopomofo-Swift.h" +#import + +@import CandidateUI; // C++ namespace usages using namespace std; @@ -191,12 +194,13 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot _languageModel->setExternalConverterEnabled(Preferences.chineseConversionStyle == 1); } -- (void)fixNodeWithValue:(std::string)value +- (void)fixNodeWithValue:(NSString *)value { size_t cursorIndex = [self _actualCandidateCursorIndex]; - _builder->grid().fixNodeSelectedCandidate(cursorIndex, value); + string stringValue = [value UTF8String]; + _builder->grid().fixNodeSelectedCandidate(cursorIndex, stringValue); if (_inputMode != kPlainBopomofoModeIdentifier) { - _userOverrideModel->observe(_walkedNodes, cursorIndex, value, [[NSDate date] timeIntervalSince1970]); + _userOverrideModel->observe(_walkedNodes, cursorIndex, stringValue, [[NSDate date] timeIntervalSince1970]); } [self _walk]; } @@ -297,7 +301,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot // update the composing buffer composeReading = _bpmfReadingBuffer->hasToneMarker(); if (!composeReading) { - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); return YES; } @@ -313,7 +317,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot // see if we have a unigram for this if (!_languageModel->hasUnigramsForKey(reading)) { errorCallback(); - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); return YES; } @@ -338,7 +342,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot // then update the text _bpmfReadingBuffer->clear(); - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; inputting.poppedText = poppedText; stateCallback(inputting); @@ -376,7 +380,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot } else if (_languageModel->hasUnigramsForKey(" ")) { _builder->insertReadingAtCursor(" "); NSString *poppedText = [self _popOverflowComposingTextAndWalk]; - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; inputting.poppedText = poppedText; stateCallback(inputting); } @@ -440,7 +444,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (_bpmfReadingBuffer->isEmpty()) { _builder->insertReadingAtCursor(string("_punctuation_list")); NSString *poppedText = [self _popOverflowComposingTextAndWalk]; - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; inputting.poppedText = poppedText; stateCallback(inputting); InputStateChoosingCandidate *choosingCandidate = [self _buildCandidateState:inputting useVerticalMode:input.useVerticalMode]; @@ -510,7 +514,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot } } else { _bpmfReadingBuffer->clear(); - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); } } @@ -543,7 +547,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot } else { if (_builder->cursorIndex() > 0) { _builder->setCursorIndex(_builder->cursorIndex() - 1); - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); } else { errorCallback(); @@ -579,7 +583,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot } else { if (_builder->cursorIndex() < _builder->length()) { _builder->setCursorIndex(_builder->cursorIndex() + 1); - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); } else { errorCallback(); @@ -604,7 +608,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (_builder->cursorIndex()) { _builder->setCursorIndex(0); - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); } else { errorCallback(); @@ -628,7 +632,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (_builder->cursorIndex() != _builder->length()) { _builder->setCursorIndex(_builder->length()); - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); } else { errorCallback(); @@ -666,7 +670,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot _bpmfReadingBuffer->backspace(); } - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; if (!inputting.composingBuffer.length) { InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); @@ -686,7 +690,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (_builder->cursorIndex() != _builder->length()) { _builder->deleteReadingAfterCursor(); [self _walk]; - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; if (!inputting.composingBuffer.length) { InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); @@ -745,7 +749,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot return YES; } - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; inputting.poppedText = poppedText; stateCallback(inputting); @@ -775,7 +779,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot UniChar charCode = input.charCode; if (charCode == 27) { - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); return YES; } @@ -786,7 +790,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot errorCallback(); return YES; } - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); return YES; } @@ -842,7 +846,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); } else { - InputStateInputting *inputting = [self _buildInputtingState]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); } return YES; @@ -1029,7 +1033,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot #pragma mark - States Building -- (InputStateInputting *)_buildInputtingState +- (InputStateInputting *)buildInputtingState { // "updating the composing buffer" means to request the client to "refresh" the text input buffer // with our "composing text" diff --git a/Source/LanguageModelManager+Privates.h b/Source/LanguageModelManager+Privates.h new file mode 100644 index 00000000..1c36fb4a --- /dev/null +++ b/Source/LanguageModelManager+Privates.h @@ -0,0 +1,13 @@ +#import "LanguageModelManager.h" +#import "UserOverrideModel.h" +#import "McBopomofoLM.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface LanguageModelManager () +@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelMcBopomofo; +@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelPlainBopomofo; +@property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel; +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index 5323d669..194c8f91 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -22,8 +22,6 @@ // OTHER DEALINGS IN THE SOFTWARE. #import -#import "UserOverrideModel.h" -#import "McBopomofoLM.h" NS_ASSUME_NONNULL_BEGIN @@ -41,9 +39,8 @@ NS_ASSUME_NONNULL_BEGIN @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathMcBopomofo; @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathPlainBopomofo; @property (class, readonly, nonatomic) NSString *phraseReplacementDataPathMcBopomofo; -@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelMcBopomofo; -@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelPlainBopomofo; -@property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel; +@property (class, assign, nonatomic) BOOL phraseReplacementEnabled; + @end NS_ASSUME_NONNULL_END diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 632617da..c36156b5 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -22,6 +22,7 @@ // OTHER DEALINGS IN THE SOFTWARE. #import "LanguageModelManager.h" +#import "LanguageModelManager+Privates.h" #import "McBopomofo-Swift.h" @import VXHanConvert; @@ -251,4 +252,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo return &gUserOverrideModel; } ++ (BOOL)phraseReplacementEnabled +{ + return gLanguageModelMcBopomofo.phraseReplacementEnabled(); +} + ++ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled +{ + gLanguageModelMcBopomofo.setPhraseReplacementEnabled(phraseReplacementEnabled); +} + @end diff --git a/Source/McBopomofo-Bridging-Header.h b/Source/McBopomofo-Bridging-Header.h index 69a7fc4f..bf326e22 100644 --- a/Source/McBopomofo-Bridging-Header.h +++ b/Source/McBopomofo-Bridging-Header.h @@ -4,9 +4,5 @@ @import Foundation; -@interface LanguageModelManager : NSObject -+ (void)loadDataModels; -+ (void)loadUserPhrases; -+ (void)loadUserPhraseReplacement; -+ (void)setupDataModelValueConverter; -@end +#import "KeyHandler.h" +#import "LanguageModelManager.h" From 4466564e25b32ccbafc7cc06391b6c91c1edf0be Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 03:50:18 +0800 Subject: [PATCH 02/15] Gives a minial cell size in the horizontal candidate window. It prevents the size of the window from being always changing when using the punctuation list. --- .../Sources/CandidateUI/HorizontalCandidateController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift index 08c39284..44e18c07 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift @@ -64,7 +64,8 @@ fileprivate class HorizontalCandidateView: NSView { for index in 0.. Date: Sun, 30 Jan 2022 03:56:30 +0800 Subject: [PATCH 03/15] Updates the copyright title and the README file. --- Source/LanguageModelManager+Privates.h | 23 +++ Source/README | 229 +++++++++++++------------ 2 files changed, 141 insertions(+), 111 deletions(-) diff --git a/Source/LanguageModelManager+Privates.h b/Source/LanguageModelManager+Privates.h index 1c36fb4a..c8723de5 100644 --- a/Source/LanguageModelManager+Privates.h +++ b/Source/LanguageModelManager+Privates.h @@ -1,3 +1,26 @@ +// Copyright (c) 2022 and onwards The McBopomofo Authors. +// +// 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: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 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 "LanguageModelManager.h" #import "UserOverrideModel.h" #import "McBopomofoLM.h" diff --git a/Source/README b/Source/README index 82384134..994fe349 100644 --- a/Source/README +++ b/Source/README @@ -1,19 +1,13 @@ . -├── AppDelegate.h -├── AppDelegate.m -├── CandidateUI -│   ├── VTCandidateController.h -│   ├── VTCandidateController.m -│   ├── VTHorizontalCandidateController.h -│   ├── VTHorizontalCandidateController.m -│   ├── VTHorizontalCandidateView.h -│   ├── VTHorizontalCandidateView.m -│   ├── VTVerticalCandidateController.h -│   ├── VTVerticalCandidateController.m -│   ├── VTVerticalCandidateTableView.h -│   ├── VTVerticalCandidateTableView.m -│   ├── VTVerticalKeyLabelStripView.h -│   └── VTVerticalKeyLabelStripView.m +├── AppDelegate.swift +├── Base.lproj +│   ├── Credits.rtf +│   ├── MainMenu.xib +│   ├── preferences.xib +│   ├── template-data.txt +│   ├── template-exclude-phrases-plain-bpmf.txt +│   ├── template-exclude-phrases.txt +│   └── template-phrases-replacement.txt ├── Data │   ├── BPMFBase.txt │   ├── BPMFMappings.txt @@ -24,19 +18,18 @@ │   ├── Symbols.txt │   ├── bin │   │   ├── C_Version -│   │   │   ├── C_count.occ.exe │   │   │   ├── Makefile │   │   │   ├── count.bash │   │   │   └── count.occurrence.c │   │   ├── README │   │   ├── Sample_Prep │   │   │   ├── build.bash -│   │   │   ├── filter.bash -│   │   │   └── filter.py +│   │   │   └── filter.bash │   │   ├── bpmfmap.py │   │   ├── buildFreq.py │   │   ├── cook-plain-bpmf.py │   │   ├── cook.py +│   │   ├── cook_util.py │   │   ├── count.bash │   │   ├── count.occurrence.py │   │   ├── disabled @@ -47,7 +40,6 @@ │   │   │   ├── cook.rb │   │   │   ├── count.occurrence.pl │   │   │   ├── countphrase.bash -│   │   │   ├── filter.bash │   │   │   ├── randomShuffle.bash │   │   │   ├── typocorrection.bash │   │   │   └── utf8length.pl @@ -55,6 +47,7 @@ │   │   ├── self-score-test.py │   │   └── textpool.rc │   ├── data-plain-bpmf.txt +│   ├── data.txt │   ├── exclusion.txt │   ├── heterophony1.list │   ├── heterophony2.list @@ -64,11 +57,9 @@ │   │   ├── covered_by_others.txt │   │   ├── covered_by_singles.txt │   │   └── falsecount.txt -│   ├── phrase.occ -│   └── problem.txt +│   └── phrase.occ ├── Engine -│   ├── FastLM.cpp -│   ├── FastLM.h +│   ├── CMakeLists.txt │   ├── Gramambular │   │   ├── Bigram.h │   │   ├── BlockReadingBuilder.h @@ -81,121 +72,137 @@ │   │   ├── Span.h │   │   ├── Unigram.h │   │   └── Walker.h +│   ├── KeyValueBlobReader.cpp +│   ├── KeyValueBlobReader.h +│   ├── KeyValueBlobReaderTest.cpp │   ├── Mandarin │   │   ├── Mandarin.cpp │   │   └── Mandarin.h -│   └── OpenVanilla -│   ├── OVAroundFilter.h -│   ├── OVBase.h -│   ├── OVBenchmark.h -│   ├── OVCINDataTable.h -│   ├── OVCINDatabaseService.h -│   ├── OVCINToSQLiteConvertor.h -│   ├── OVCandidateService.h -│   ├── OVDatabaseService.h -│   ├── OVDateTimeHelper.h -│   ├── OVEncodingService.h -│   ├── OVEventHandlingContext.h -│   ├── OVException.h -│   ├── OVFileHelper.h -│   ├── OVFrameworkInfo.h -│   ├── OVInputMethod.h -│   ├── OVKey.h -│   ├── OVKeyPreprocessor.h -│   ├── OVKeyValueMap.h -│   ├── OVLoaderBase.h -│   ├── OVLoaderService.h -│   ├── OVLocalization.h -│   ├── OVModule.h -│   ├── OVModulePackage.h -│   ├── OVOutputFilter.h -│   ├── OVPathInfo.h -│   ├── OVSQLiteDatabaseService.h -│   ├── OVSQLiteWrapper.h -│   ├── OVStringHelper.h -│   ├── OVTextBuffer.h -│   ├── OVUTF8Helper.h -│   ├── OVWildcard.h -│   └── OpenVanilla.h -├── IconMaker -│   ├── AppIconRendererView.h -│   ├── AppIconRendererView.m -│   ├── BopomofoIconRenderView.h -│   ├── BopomofoIconRenderView.m -│   ├── IconMaker-Info.plist -│   ├── IconMaker-Prefix.pch -│   ├── IconMaker.xcodeproj -│   │   └── project.pbxproj -│   ├── IconMakerAppDelegate.h -│   ├── IconMakerAppDelegate.m -│   ├── ImageZoomInView.h -│   ├── ImageZoomInView.m -│   ├── TISIconRendererView.h -│   ├── TISIconRendererView.m -│   ├── en.lproj -│   │   └── MainMenu.xib -│   └── main.m +│   ├── McBopomofoLM.cpp +│   ├── McBopomofoLM.h +│   ├── OpenVanilla +│   │   ├── OVAroundFilter.h +│   │   ├── OVBase.h +│   │   ├── OVBenchmark.h +│   │   ├── OVCINDataTable.h +│   │   ├── OVCINDatabaseService.h +│   │   ├── OVCINToSQLiteConvertor.h +│   │   ├── OVCandidateService.h +│   │   ├── OVDatabaseService.h +│   │   ├── OVDateTimeHelper.h +│   │   ├── OVEncodingService.h +│   │   ├── OVEventHandlingContext.h +│   │   ├── OVException.h +│   │   ├── OVFileHelper.h +│   │   ├── OVFrameworkInfo.h +│   │   ├── OVInputMethod.h +│   │   ├── OVKey.h +│   │   ├── OVKeyPreprocessor.h +│   │   ├── OVKeyValueMap.h +│   │   ├── OVLoaderBase.h +│   │   ├── OVLoaderService.h +│   │   ├── OVLocalization.h +│   │   ├── OVModule.h +│   │   ├── OVModulePackage.h +│   │   ├── OVOutputFilter.h +│   │   ├── OVPathInfo.h +│   │   ├── OVSQLiteDatabaseService.h +│   │   ├── OVSQLiteWrapper.h +│   │   ├── OVStringHelper.h +│   │   ├── OVTextBuffer.h +│   │   ├── OVUTF8Helper.h +│   │   ├── OVWildcard.h +│   │   └── OpenVanilla.h +│   ├── ParselessLM.cpp +│   ├── ParselessLM.h +│   ├── ParselessLMBenchmark.cpp +│   ├── ParselessLMTest.cpp +│   ├── ParselessPhraseDB.cpp +│   ├── ParselessPhraseDB.h +│   ├── ParselessPhraseDBTest.cpp +│   ├── PhraseReplacementMap.cpp +│   ├── PhraseReplacementMap.h +│   ├── PhraseReplacementMapTest.cpp +│   ├── UserOverrideModel.cpp +│   ├── UserOverrideModel.h +│   ├── UserPhrasesLM.cpp +│   ├── UserPhrasesLM.h +│   └── UserPhrasesLMTest.cpp ├── Images │   ├── Bopomofo.tiff │   ├── Bopomofo@2x.tiff -│   ├── BopomofoTextMenu.tiff -│   ├── BopomofoTextMenu@2x.tiff -│   ├── McBopomofo.iconset -│   │   ├── icon_128x128.png -│   │   ├── icon_128x128@2x.png -│   │   ├── icon_16x16.png -│   │   ├── icon_16x16@2x.png -│   │   ├── icon_256x256.png -│   │   ├── icon_256x256@2x.png -│   │   ├── icon_32x32.png -│   │   ├── icon_32x32@2x.png -│   │   ├── icon_512x512.png -│   │   └── icon_512x512@2x.png +│   ├── Images.xcassets +│   │   ├── AlertIcon.imageset +│   │   │   ├── 128X128.png +│   │   │   ├── 192x192.png +│   │   │   ├── 64X64.png +│   │   │   └── Contents.json +│   │   ├── AppIcon.appiconset +│   │   │   ├── 1024X1024.png +│   │   │   ├── 128X128.png +│   │   │   ├── 16X16.png +│   │   │   ├── 256X256.png +│   │   │   ├── 32X32.png +│   │   │   ├── 512X512.png +│   │   │   ├── 64X64.png +│   │   │   └── Contents.json +│   │   └── Contents.json │   ├── PlainBopomofo.tiff -│   ├── PlainBopomofo@2x.tiff -│   ├── favicon.ico -│   ├── favicon.png -│   └── favicon.tiff -├── InputMethodController.h -├── InputMethodController.mm +│   └── PlainBopomofo@2x.tiff +├── InputMethodController.swift +├── InputState.swift ├── Installer -│   ├── AppDelegate.h -│   ├── AppDelegate.m +│   ├── AppDelegate.swift +│   ├── ArchiveUtil.swift +│   ├── Base.lproj +│   │   └── MainMenu.xib +│   ├── BundleTranslocate.h +│   ├── BundleTranslocate.m │   ├── Installer-Info.plist │   ├── Installer-Prefix.pch +│   ├── McBopomofoInstaller-Bridging-Header.h +│   ├── NotarizedArchives +│   │   └── README.md │   ├── en.lproj │   │   ├── InfoPlist.strings │   │   ├── License.rtf -│   │   ├── Localizable.strings -│   │   └── MainMenu.xib -│   ├── main.m +│   │   └── Localizable.strings │   └── zh-Hant.lproj │   ├── InfoPlist.strings │   ├── License.rtf │   ├── Localizable.strings │   └── MainMenu.xib +├── KeyHandler.h +├── KeyHandler.mm +├── KeyHandlerInput.swift +├── LanguageModelManager+Privates.h +├── LanguageModelManager.h +├── LanguageModelManager.mm +├── McBopomofo-Bridging-Header.h ├── McBopomofo-Info.plist ├── McBopomofo-Prefix.pch -├── OVInputSourceHelper.h -├── OVInputSourceHelper.m -├── PreferencesWindowController.h -├── PreferencesWindowController.m +├── NonModalAlertWindowController.swift +├── NonModalAlertWindowController.xib +├── Preferences.swift +├── PreferencesWindowController.swift ├── README ├── Tools │   ├── genRTF.py │   └── tistool.m -├── UpdateNotificationController.h -├── UpdateNotificationController.m ├── en.lproj +│   ├── Credits.rtf │   ├── InfoPlist.strings -│   ├── Localizable.strings -│   ├── MainMenu.xib -│   ├── UpdateNotificationController.xib -│   └── preferences.xib -├── main.m +│   └── Localizable.strings +├── main.swift └── zh-Hant.lproj + ├── Credits.rtf ├── InfoPlist.strings ├── Localizable.strings ├── MainMenu.xib - ├── UpdateNotificationController.xib - └── preferences.xib + ├── preferences.xib + ├── template-data.txt + ├── template-exclude-phrases-plain-bpmf.txt + ├── template-exclude-phrases.txt + └── template-phrases-replacement.txt + +23 directories, 182 files From 0b1975bfeb626b666ba54c02d9aeb30a0c9a8081 Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 05:10:15 +0800 Subject: [PATCH 04/15] Converts test cases to Swift. --- McBopomofo.xcodeproj/project.pbxproj | 40 +- McBopomofoTests/KeyHandlerBopomofoTests.mm | 655 ------------------ McBopomofoTests/KeyHandlerBopomofoTests.swift | 437 ++++++++++++ .../McBopomofoTests-Bridging-Header.h | 4 - McBopomofoTests/PreferencesTests.swift | 2 +- Source/InputMethodController.swift | 14 +- 6 files changed, 449 insertions(+), 703 deletions(-) delete mode 100644 McBopomofoTests/KeyHandlerBopomofoTests.mm create mode 100644 McBopomofoTests/KeyHandlerBopomofoTests.swift delete mode 100644 McBopomofoTests/McBopomofoTests-Bridging-Header.h diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 2b04991d..31ea286c 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; 6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; }; 6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; }; 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; }; @@ -19,12 +18,10 @@ 6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; 6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; 6A38BC1515FC117A00A8A51F /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; }; - 6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; }; 6A6ED16B2797650A0012872E /* template-phrases-replacement.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1632797650A0012872E /* template-phrases-replacement.txt */; }; 6A6ED16C2797650A0012872E /* template-data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1652797650A0012872E /* template-data.txt */; }; 6A6ED16D2797650A0012872E /* template-exclude-phrases-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1672797650A0012872E /* template-exclude-phrases-plain-bpmf.txt */; }; 6A6ED16E2797650A0012872E /* template-exclude-phrases.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1692797650A0012872E /* template-exclude-phrases.txt */; }; - 6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; 6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; }; 6ACA41FB15FC1D9000935EF6 /* License.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EC15FC1D9000935EF6 /* License.rtf */; }; 6ACA41FC15FC1D9000935EF6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EE15FC1D9000935EF6 /* Localizable.strings */; }; @@ -53,6 +50,7 @@ D456576E279E4F7B00DF6BC9 /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */; }; D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; }; D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; }; + D47D73A427A5D43900255A50 /* KeyHandlerBopomofoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */; }; D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; }; D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; }; D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; }; @@ -60,10 +58,6 @@ D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */; }; D4A13D5A27A59F0B003BE359 /* InputMethodController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A13D5927A59D5C003BE359 /* InputMethodController.swift */; }; D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */; }; - D4E569DF27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DE27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm */; }; - D4E569E027A4123200AC2CEF /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */; }; - D4E569E127A4128300AC2CEF /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; }; - D4E569E227A412E700AC2CEF /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74427915555003C80A6 /* Preferences.swift */; }; D4E569E427A414CB00AC2CEF /* data-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6AD7CBC715FE555000691B5B /* data-plain-bpmf.txt */; }; D4E569E527A414CB00AC2CEF /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; }; D4F0BBDF279AF1AF0071253C /* ArchiveUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */; }; @@ -98,9 +92,6 @@ /* Begin PBXFileReference section */ 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = McBopomofo.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; - 6A0D4EA915FC0D2D00ABF4B3 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; - 6A0D4EAB15FC0D2D00ABF4B3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Bopomofo.tiff; sourceTree = ""; }; 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "Bopomofo@2x.tiff"; sourceTree = ""; }; 6A0D4EF515FC0DA600ABF4B3 /* McBopomofo-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "McBopomofo-Info.plist"; sourceTree = ""; }; @@ -162,7 +153,6 @@ 6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = ""; }; 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 6A38BBF615FC117A00A8A51F /* data.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = data.txt; sourceTree = ""; }; - 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/System/Library/Frameworks/InputMethodKit.framework; sourceTree = DEVELOPER_DIR; }; 6A6ED1642797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-phrases-replacement.txt"; sourceTree = ""; }; 6A6ED1662797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-data.txt"; sourceTree = ""; }; 6A6ED1682797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-exclude-phrases-plain-bpmf.txt"; sourceTree = ""; }; @@ -212,6 +202,7 @@ D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerInput.swift; sourceTree = ""; }; D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = ""; }; D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerBopomofoTests.swift; sourceTree = ""; }; D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; @@ -223,8 +214,6 @@ D4A13D5927A59D5C003BE359 /* InputMethodController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputMethodController.swift; 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 = ""; }; - D4E569DD27A40F1300AC2CEF /* McBopomofoTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofoTests-Bridging-Header.h"; sourceTree = ""; }; - D4E569DE27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyHandlerBopomofoTests.mm; sourceTree = ""; }; 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 /* BundleTranslocate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BundleTranslocate.h; sourceTree = ""; }; @@ -242,11 +231,9 @@ D427F7B4279086DC004A2160 /* InputSourceHelper in Frameworks */, D427F7C127908EFC004A2160 /* OpenCCBridge in Frameworks */, D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */, - 6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */, D427F7A927905E90004A2160 /* TooltipUI in Frameworks */, D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */, D427F7AE27907B8A004A2160 /* NotifierUI in Frameworks */, - 6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -255,7 +242,6 @@ buildActionMask = 2147483647; files = ( D427F7B6279086F6004A2160 /* InputSourceHelper in Frameworks */, - 6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -275,7 +261,6 @@ D427F766278C9CBD004A2160 /* Packages */, 6A0D4EC215FC0D3C00ABF4B3 /* Source */, D485D3B72796A8A000657FF3 /* McBopomofoTests */, - 6A0D4EA515FC0D2D00ABF4B3 /* Frameworks */, 6A0D4EA315FC0D2D00ABF4B3 /* Products */, ); sourceTree = ""; @@ -290,17 +275,6 @@ name = Products; sourceTree = ""; }; - 6A0D4EA515FC0D2D00ABF4B3 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 6A0D4EA915FC0D2D00ABF4B3 /* AppKit.framework */, - 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */, - 6A0D4EAB15FC0D2D00ABF4B3 /* Foundation.framework */, - 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 6A0D4EC215FC0D3C00ABF4B3 /* Source */ = { isa = PBXGroup; children = ( @@ -500,10 +474,9 @@ D485D3B72796A8A000657FF3 /* McBopomofoTests */ = { isa = PBXGroup; children = ( + D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */, D485D3B82796A8A000657FF3 /* PreferencesTests.swift */, D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */, - D4E569DE27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm */, - D4E569DD27A40F1300AC2CEF /* McBopomofoTests-Bridging-Header.h */, ); path = McBopomofoTests; sourceTree = ""; @@ -750,12 +723,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D4E569DF27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm in Sources */, + D47D73A427A5D43900255A50 /* KeyHandlerBopomofoTests.swift in Sources */, D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */, - D4E569E227A412E700AC2CEF /* Preferences.swift in Sources */, - D4E569E127A4128300AC2CEF /* InputState.swift in Sources */, D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */, - D4E569E027A4123200AC2CEF /* KeyHandlerInput.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1288,7 +1258,6 @@ SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_OBJC_BRIDGING_HEADER = "McBopomofoTests/McBopomofoTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/McBopomofo.app/Contents/MacOS/McBopomofo"; @@ -1333,7 +1302,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_OBJC_BRIDGING_HEADER = "McBopomofoTests/McBopomofoTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/McBopomofo.app/Contents/MacOS/McBopomofo"; diff --git a/McBopomofoTests/KeyHandlerBopomofoTests.mm b/McBopomofoTests/KeyHandlerBopomofoTests.mm deleted file mode 100644 index 7c2589cf..00000000 --- a/McBopomofoTests/KeyHandlerBopomofoTests.mm +++ /dev/null @@ -1,655 +0,0 @@ -#import -#import "KeyHandler.h" -#import "LanguageModelManager.h" -#import "McBopomofoTests-Swift.h" - -@interface KeyHandlerBopomofoTests : XCTestCase - -@end - -@implementation KeyHandlerBopomofoTests - -- (void)setUp -{ - [LanguageModelManager loadDataModels]; -} - -- (void)tearDown -{ -} - -- (void)testPunctuationComma -{ - KeyHandler *handler = [[KeyHandler alloc] init]; - handler.inputMode = kBopomofoModeIdentifier; - - KeyHandlerInput *input; - __block InputState *state; - state = [[InputStateEmpty alloc] init]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"<" keyCode:0 charCode:'<' flags:NSEventModifierFlagShift isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class])); - NSString *composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@","], @"It should be , but %@", composingBuffer); -} - -- (void)testPunctuationPeriod -{ - KeyHandler *handler = [[KeyHandler alloc] init]; - handler.inputMode = kBopomofoModeIdentifier; - - KeyHandlerInput *input; - __block InputState *state; - state = [[InputStateEmpty alloc] init]; - - input = [[KeyHandlerInput alloc] initWithInputText:@">" keyCode:0 charCode:'>' flags:NSEventModifierFlagShift isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class])); - NSString *composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"。"], @"It should be 。 but %@", composingBuffer); -} - -- (void)testInputtingNihao -{ - KeyHandler *handler = [[KeyHandler alloc] init]; - handler.inputMode = kBopomofoModeIdentifier; - - KeyHandlerInput *input; - __block InputState *state; - state = [[InputStateEmpty alloc] init]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class])); - NSString *composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); -} - -- (void)testCommittingNihao -{ - KeyHandler *handler = [[KeyHandler alloc] init]; - handler.inputMode = kBopomofoModeIdentifier; - - KeyHandlerInput *input; - __block InputState *state; - state = [[InputStateEmpty alloc] init]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - __block NSInteger count = 0; - - __block InputState *empty; - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:0 charCode:13 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - if (!count) { - state = inState; - } - empty = inState; - count++; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateCommitting")], @"It should be a committing state %@.", NSStringFromClass([state class])); - NSString *poppedText = [(InputStateCommitting *)state poppedText]; - XCTAssertTrue([poppedText isEqualToString:@"你好"], @"It should be 你好 but %@", poppedText); - - XCTAssertTrue([empty isKindOfClass:NSClassFromString(@"McBopomofo.InputStateEmpty")], @"It should be an empty state %@.", NSStringFromClass([state class])); -} - -- (void)testDelete -{ - KeyHandler *handler = [[KeyHandler alloc] init]; - handler.inputMode = kBopomofoModeIdentifier; - - KeyHandlerInput *input; - __block InputState *state; - state = [[InputStateEmpty alloc] init]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class])); - NSString *composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2); - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:123 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 1); - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:117 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你"], @"It should be 你 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 1); - - __block BOOL errorCalled = NO; - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:117 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - errorCalled = YES; - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你"], @"It should be 你 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 1); - XCTAssertTrue(errorCalled); - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:123 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你"], @"It should be 你 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 0); - - errorCalled = NO; - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:117 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - errorCalled = YES; - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateEmptyIgnoringPreviousState")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - XCTAssertFalse(errorCalled); -} - -- (void)testBackspace -{ - KeyHandler *handler = [[KeyHandler alloc] init]; - handler.inputMode = kBopomofoModeIdentifier; - - KeyHandlerInput *input; - __block InputState *state; - state = [[InputStateEmpty alloc] init]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class])); - NSString *composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:0 charCode:8 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你"], @"It should be 你 but %@", composingBuffer); - - __block InputStateEmpty *empty; - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:0 charCode:8 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - empty = (InputStateEmpty *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([empty isKindOfClass:NSClassFromString(@"McBopomofo.InputStateEmptyIgnoringPreviousState")], @"It should be a inputting state %@.", NSStringFromClass([state class])); -} - -- (void)testCursor -{ - KeyHandler *handler = [[KeyHandler alloc] init]; - handler.inputMode = kBopomofoModeIdentifier; - - KeyHandlerInput *input; - __block InputState *state; - state = [[InputStateEmpty alloc] init]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class])); - NSString *composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2); - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:123 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 1); - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:123 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 0); - - __block BOOL errorCalled = NO; - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:123 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - errorCalled = YES; - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 0); - XCTAssertTrue(errorCalled); - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:124 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 1); - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:124 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2); - - errorCalled = NO; - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:124 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = (InputStateInputting *)inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - errorCalled = YES; - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2); - XCTAssertTrue(errorCalled); -} - -- (void)testCandidateWithDown -{ - KeyHandler *handler = [[KeyHandler alloc] init]; - handler.inputMode = kBopomofoModeIdentifier; - - KeyHandlerInput *input; - __block InputState *state; - state = [[InputStateEmpty alloc] init]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:125 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateChoosingCandidate")], @"It should be a inputting state %@.", NSStringFromClass([state class])); - NSArray *candidates = [(InputStateChoosingCandidate *)state candidates]; - XCTAssertTrue([candidates containsObject:@"你"]); - -} - -- (void)testHomeAndEnd -{ - KeyHandler *handler = [[KeyHandler alloc] init]; - handler.inputMode = kBopomofoModeIdentifier; - - KeyHandlerInput *input; - __block InputState *state; - state = [[InputStateEmpty alloc] init]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class])); - NSString *composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2); - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:115 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 0); - - input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:119 charCode:0 flags:0 isVerticalMode:0]; - [handler handleInput:input state:state stateCallback:^(InputState * inState) { - state = inState; - } candidateSelectionCallback:^{ - } errorCallback:^{ - }]; - - XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class])); - composingBuffer = [(InputStateInputting *)state composingBuffer]; - XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer); - XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2); - -} - -@end - diff --git a/McBopomofoTests/KeyHandlerBopomofoTests.swift b/McBopomofoTests/KeyHandlerBopomofoTests.swift new file mode 100644 index 00000000..da7f6baa --- /dev/null +++ b/McBopomofoTests/KeyHandlerBopomofoTests.swift @@ -0,0 +1,437 @@ +import XCTest +@testable import McBopomofo + +func charCode(_ string: String) -> UInt16 { + let scalars = string.unicodeScalars + return UInt16(scalars[scalars.startIndex].value) +} + +class KeyHandlerBopomofoTests: XCTestCase { + var handler = KeyHandler() + + override func setUpWithError() throws { + LanguageModelManager.loadDataModels() + handler = KeyHandler() + handler.inputMode = kBopomofoModeIdentifier + } + + override func tearDownWithError() throws { + } + + func testPunctuationComma() { + let input = KeyHandlerInput(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, ",") + } + } + + func testPunctuationPeriod() { + let input = KeyHandlerInput(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "。") + } + } + + func testInputting() { + var state: InputState = InputState.Empty() + let keys = Array("vul3a945j4up gj bj4z83").map { String($0) } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "小麥注音輸入法") + } + } + + func testInputtingNihao() { + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { String($0) } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + } + } + + func testInputtingTianKong() { + var state: InputState = InputState.Empty() + let keys = Array("wu0 dj/ ").map { String($0) } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "天空") + } + } + + func testCommittingNihao() { + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { String($0) } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + } + + let enter = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 13, flags: [], isVerticalMode: false) + var committing: InputState? + var empty: InputState? + var count = 0 + + handler.handle(enter, state: state) { newState in + switch count { + case 0: + committing = newState + case 1: + empty = newState + default: + break + } + count += 1 + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(committing is InputState.Committing, "\(state)") + if let committing = committing as? InputState.Committing { + XCTAssertEqual(committing.poppedText, "你好") + } + XCTAssertTrue(empty is InputState.Empty, "\(state)") + } + + func testDelete() { + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { String($0) } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + let left = KeyHandlerInput(inputText: " ", keyCode: KeyCode.left.rawValue, charCode: 0, flags: [], isVerticalMode: false) + let delete = KeyHandlerInput(inputText: " ", keyCode: KeyCode.delete.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var errorCalled = false + + handler.handle(left, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 1) + } + + handler.handle(delete, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + + handler.handle(delete, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + errorCalled = true + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + XCTAssertTrue(errorCalled) + + errorCalled = false + + handler.handle(left, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 0) + } + + handler.handle(delete, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + } + + func testBackspace() { + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { String($0) } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + let backspace = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 8, flags: [], isVerticalMode: false) + + handler.handle(backspace, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + + handler.handle(backspace, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + } + + func testCursor() { + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { String($0) } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + let left = KeyHandlerInput(inputText: " ", keyCode: KeyCode.left.rawValue, charCode: 0, flags: [], isVerticalMode: false) + let right = KeyHandlerInput(inputText: " ", keyCode: KeyCode.right.rawValue, charCode: 0, flags: [], isVerticalMode: false) + + var errorCalled = false + + handler.handle(left, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 1) + } + + handler.handle(left, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + } + + handler.handle(left, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + errorCalled = true + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + } + XCTAssertTrue(errorCalled) + + handler.handle(right, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 1) + } + + handler.handle(right, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + errorCalled = false + handler.handle(right, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + errorCalled = true + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + XCTAssertTrue(errorCalled) + } + + func testCandidateWithDown() { + var state: InputState = InputState.Empty() + let keys = Array("su3").map { String($0) } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + + let space = KeyHandlerInput(inputText: " ", keyCode: KeyCode.down.rawValue, charCode: 0, flags: [], isVerticalMode: false) + handler.handle(space, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + let candidates = state.candidates + XCTAssertTrue(candidates.contains("你")) + } + } + + func testHomeAndEnd() { + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { String($0) } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + let home = KeyHandlerInput(inputText: " ", keyCode: KeyCode.home.rawValue, charCode: 0, flags: [], isVerticalMode: false) + let end = KeyHandlerInput(inputText: " ", keyCode: KeyCode.end.rawValue, charCode: 0, flags: [], isVerticalMode: false) + + handler.handle(home, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + } + + handler.handle(end, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + } + +} diff --git a/McBopomofoTests/McBopomofoTests-Bridging-Header.h b/McBopomofoTests/McBopomofoTests-Bridging-Header.h deleted file mode 100644 index 1b2cb5d6..00000000 --- a/McBopomofoTests/McBopomofoTests-Bridging-Header.h +++ /dev/null @@ -1,4 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// - diff --git a/McBopomofoTests/PreferencesTests.swift b/McBopomofoTests/PreferencesTests.swift index 5d966f6d..03e71781 100644 --- a/McBopomofoTests/PreferencesTests.swift +++ b/McBopomofoTests/PreferencesTests.swift @@ -1,5 +1,5 @@ import XCTest -//@testable import McBopomofo +@testable import McBopomofo class PreferencesTests: XCTestCase { diff --git a/Source/InputMethodController.swift b/Source/InputMethodController.swift index 5663529d..c0d83dec 100644 --- a/Source/InputMethodController.swift +++ b/Source/InputMethodController.swift @@ -77,7 +77,7 @@ class McBopomofoInputMethodController: IMKInputController { let optionKeyPressed = NSEvent.modifierFlags.contains(.option) if inputMode == kBopomofoModeIdentifier && optionKeyPressed { - let phaseReplacementItem = menu.addItem(withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), action: #selector(togglePhraseReplacementEnabled(_:)), keyEquivalent: "") + let phaseReplacementItem = menu.addItem(withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "") phaseReplacementItem.state = Preferences.phraseReplacementEnabled.state } @@ -199,7 +199,7 @@ class McBopomofoInputMethodController: IMKInputController { NotifierController.notify(message: enabled ? NSLocalizedString("Half-width punctuation on", comment: "") : NSLocalizedString("Half-width punctuation off", comment: "")) } - @objc func togglePhraseReplacementEnabled(_ sender: Any?) { + @objc func togglePhraseReplacement(_ sender: Any?) { let enabled = Preferences.togglePhraseReplacementEnabled() LanguageModelManager.phraseReplacementEnabled = enabled } @@ -209,7 +209,7 @@ class McBopomofoInputMethodController: IMKInputController { } private func open(userFileAt path: String) { - func checkUserFiles() -> Bool { + func checkIfUserFilesExist() -> Bool { if !LanguageModelManager.checkIfUserLanguageModelFilesExist() { let content = String(format: NSLocalizedString("Please check the permission of at \"%@\".", comment: ""), LanguageModelManager.dataFolderPath) NonModalAlertWindowController.shared.show(title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) @@ -218,7 +218,7 @@ class McBopomofoInputMethodController: IMKInputController { return true } - if !checkUserFiles() { + if !checkIfUserFilesExist() { return } let url = URL(fileURLWithPath: path) @@ -429,15 +429,15 @@ extension McBopomofoInputMethodController { let textSize = Preferences.candidateListTextSize let keyLabelSize = max(textSize / 2, kMinKeyLabelSize) - func fallbackFont(name: String?, size: CGFloat) -> NSFont { + func font(name: String?, size: CGFloat) -> NSFont { if let name = name { return NSFont(name: name, size: size) ?? NSFont.systemFont(ofSize: size) } return NSFont.systemFont(ofSize: size) } - gCurrentCandidateController?.keyLabelFont = fallbackFont(name: Preferences.candidateKeyLabelFontName, size: keyLabelSize) - gCurrentCandidateController?.candidateFont = fallbackFont(name: Preferences.candidateTextFontName, size: textSize) + gCurrentCandidateController?.keyLabelFont = font(name: Preferences.candidateKeyLabelFontName, size: keyLabelSize) + gCurrentCandidateController?.candidateFont = font(name: Preferences.candidateTextFontName, size: textSize) let candidateKeys = Preferences.candidateKeys let keyLabels = candidateKeys.count > 4 ? Array(candidateKeys) : Array(Preferences.defaultCandidateKeys) From c3d953c618ab19c52680356e404008c8ce14017d Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 05:42:16 +0800 Subject: [PATCH 05/15] Converts input mode into a typed enum. --- Source/Engine/McBopomofoLM.h | 2 +- Source/InputMethodController.swift | 8 ++++---- Source/KeyHandler.h | 7 ++++--- Source/KeyHandler.mm | 26 +++++++++++++------------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Source/Engine/McBopomofoLM.h b/Source/Engine/McBopomofoLM.h index de90a4a3..31bcb0f2 100644 --- a/Source/Engine/McBopomofoLM.h +++ b/Source/Engine/McBopomofoLM.h @@ -84,7 +84,7 @@ public: /// Enables or disables phrase replacement. void setPhraseReplacementEnabled(bool enabled); - /// If phrease replacement is enabled or not. + /// If phrase replacement is enabled or not. bool phraseReplacementEnabled(); /// Enables or disables the external converter. diff --git a/Source/InputMethodController.swift b/Source/InputMethodController.swift index c0d83dec..fa09c84e 100644 --- a/Source/InputMethodController.swift +++ b/Source/InputMethodController.swift @@ -76,7 +76,7 @@ class McBopomofoInputMethodController: IMKInputController { let inputMode = keyHandler.inputMode let optionKeyPressed = NSEvent.modifierFlags.contains(.option) - if inputMode == kBopomofoModeIdentifier && optionKeyPressed { + if inputMode == .bopomofo && optionKeyPressed { let phaseReplacementItem = menu.addItem(withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "") phaseReplacementItem.state = Preferences.phraseReplacementEnabled.state } @@ -84,7 +84,7 @@ class McBopomofoInputMethodController: IMKInputController { menu.addItem(NSMenuItem.separator()) menu.addItem(withTitle: NSLocalizedString("User Phrases", comment: ""), action: nil, keyEquivalent: "") - if inputMode == kPlainBopomofoModeIdentifier { + if inputMode == .plainBopomofo { menu.addItem(withTitle: NSLocalizedString("Edit Excluded Phrases", comment: ""), action: #selector(openExcludedPhrasesPlainBopomofo(_:)), keyEquivalent: "") } else { menu.addItem(withTitle: NSLocalizedString("Edit User Phrases", comment: ""), action: #selector(openUserPhrases(_:)), keyEquivalent: "") @@ -126,7 +126,7 @@ class McBopomofoInputMethodController: IMKInputController { } override func setValue(_ value: Any!, forTag tag: Int, client: Any!) { - let newInputMode = value as? String ?? kBopomofoModeIdentifier + let newInputMode = InputMode(rawValue: value as? String ?? InputMode.bopomofo.rawValue) if keyHandler.inputMode != newInputMode { UserDefaults.standard.synchronize() // Remember to override the keyboard layout again -- treat this as an activate event. @@ -535,7 +535,7 @@ extension McBopomofoInputMethodController: CandidateControllerDelegate { return } - if keyHandler.inputMode == kPlainBopomofoModeIdentifier { + if keyHandler.inputMode == .plainBopomofo { keyHandler.clear() handle(state: .Committing(poppedText: inputting.composingBuffer), client: currentCandidateClient) handle(state: .Empty(), client: currentDeferredClient) diff --git a/Source/KeyHandler.h b/Source/KeyHandler.h index b0e14728..7613785c 100644 --- a/Source/KeyHandler.h +++ b/Source/KeyHandler.h @@ -28,8 +28,9 @@ NS_ASSUME_NONNULL_BEGIN -extern NSString *const kBopomofoModeIdentifier; -extern NSString *const kPlainBopomofoModeIdentifier; +typedef NSString *const InputMode NS_TYPED_ENUM; +extern InputMode InputModeBopomofo; +extern InputMode InputModePlainBopomofo; @class KeyHandler; @@ -53,7 +54,7 @@ candidateSelectionCallback:(void (^)(void))candidateSelectionCallback - (InputState *)buildInputtingState; -@property (strong, nonatomic) NSString *inputMode; +@property (strong, nonatomic) InputMode inputMode; @property (weak, nonatomic) id delegate; @end diff --git a/Source/KeyHandler.mm b/Source/KeyHandler.mm index be12831d..478753ea 100644 --- a/Source/KeyHandler.mm +++ b/Source/KeyHandler.mm @@ -40,8 +40,8 @@ using namespace Formosa::Gramambular; using namespace McBopomofo; using namespace OpenVanilla; -NSString *const kBopomofoModeIdentifier = @"org.openvanilla.inputmethod.McBopomofo.Bopomofo"; -NSString *const kPlainBopomofoModeIdentifier = @"org.openvanilla.inputmethod.McBopomofo.PlainBopomofo"; +InputMode InputModeBopomofo = @"org.openvanilla.inputmethod.McBopomofo.Bopomofo"; +InputMode InputModePlainBopomofo = @"org.openvanilla.inputmethod.McBopomofo.PlainBopomofo"; static const double kEpsilon = 0.000001; @@ -105,12 +105,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot NSString *newInputMode; McBopomofoLM *newLanguageModel; - if ([value isKindOfClass:[NSString class]] && [value isEqual:kPlainBopomofoModeIdentifier]) { - newInputMode = kPlainBopomofoModeIdentifier; + if ([value isKindOfClass:[NSString class]] && [value isEqual:InputModePlainBopomofo]) { + newInputMode = InputModePlainBopomofo; newLanguageModel = [LanguageModelManager languageModelPlainBopomofo]; newLanguageModel->setPhraseReplacementEnabled(false); } else { - newInputMode = kBopomofoModeIdentifier; + newInputMode = InputModeBopomofo; newLanguageModel = [LanguageModelManager languageModelMcBopomofo]; newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); } @@ -160,7 +160,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); - _inputMode = kBopomofoModeIdentifier; + _inputMode = InputModeBopomofo; } return self; } @@ -199,7 +199,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot size_t cursorIndex = [self _actualCandidateCursorIndex]; string stringValue = [value UTF8String]; _builder->grid().fixNodeSelectedCandidate(cursorIndex, stringValue); - if (_inputMode != kPlainBopomofoModeIdentifier) { + if (_inputMode != InputModePlainBopomofo) { _userOverrideModel->observe(_walkedNodes, cursorIndex, stringValue, [[NSDate date] timeIntervalSince1970]); } [self _walk]; @@ -329,7 +329,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot NSString *poppedText = [self _popOverflowComposingTextAndWalk]; // get user override model suggestion - string overrideValue = (_inputMode == kPlainBopomofoModeIdentifier) ? "" : + string overrideValue = (_inputMode == InputModePlainBopomofo) ? "" : _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); if (!overrideValue.empty()) { @@ -346,7 +346,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot inputting.poppedText = poppedText; stateCallback(inputting); - if (_inputMode == kPlainBopomofoModeIdentifier) { + if (_inputMode == InputModePlainBopomofo) { InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:inputting useVerticalMode:input.useVerticalMode]; if (choosingCandidates.candidates.count == 1) { [self clear]; @@ -712,7 +712,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleEnterWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { if ([state isKindOfClass:[InputStateInputting class]]) { - if (_inputMode == kPlainBopomofoModeIdentifier) { + if (_inputMode == InputModePlainBopomofo) { if (!_bpmfReadingBuffer->isEmpty()) { errorCallback(); } @@ -753,7 +753,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot inputting.poppedText = poppedText; stateCallback(inputting); - if (_inputMode == kPlainBopomofoModeIdentifier && _bpmfReadingBuffer->isEmpty()) { + if (_inputMode == InputModePlainBopomofo && _bpmfReadingBuffer->isEmpty()) { InputStateChoosingCandidate *candidateState = [self _buildCandidateState:inputting useVerticalMode:useVerticalMode]; if ([candidateState.candidates count] == 1) { @@ -841,7 +841,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot BOOL cancelCandidateKey = (charCode == 27) || (charCode == 8) || [input isDelete]; if (cancelCandidateKey) { - if (_inputMode == kPlainBopomofoModeIdentifier) { + if (_inputMode == InputModePlainBopomofo) { [self clear]; InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); @@ -997,7 +997,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot } } - if (_inputMode == kPlainBopomofoModeIdentifier) { + if (_inputMode == InputModePlainBopomofo) { string layout = [self _currentLayout]; string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_") : string("_punctuation_"); string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode); From 2c0a12f43ff0ee8ff8606f34ba832ffc1a0e4a58 Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 06:38:16 +0800 Subject: [PATCH 06/15] Lazy loads language models. --- Source/AppDelegate.swift | 1 - Source/InputMethodController.swift | 1 + Source/LanguageModelManager.h | 2 ++ Source/LanguageModelManager.mm | 10 ++++++++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index f644cf39..68e8f044 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -153,7 +153,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle func applicationDidFinishLaunching(_ notification: Notification) { LanguageModelManager.setupDataModelValueConverter() - LanguageModelManager.loadDataModels() LanguageModelManager.loadUserPhrases() LanguageModelManager.loadUserPhraseReplacement() diff --git a/Source/InputMethodController.swift b/Source/InputMethodController.swift index fa09c84e..aeed9c81 100644 --- a/Source/InputMethodController.swift +++ b/Source/InputMethodController.swift @@ -127,6 +127,7 @@ class McBopomofoInputMethodController: IMKInputController { override func setValue(_ value: Any!, forTag tag: Int, client: Any!) { let newInputMode = InputMode(rawValue: value as? String ?? InputMode.bopomofo.rawValue) + LanguageModelManager.loadDataModel(newInputMode) if keyHandler.inputMode != newInputMode { UserDefaults.standard.synchronize() // Remember to override the keyboard layout again -- treat this as an activate event. diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index 194c8f91..654bee08 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -22,12 +22,14 @@ // OTHER DEALINGS IN THE SOFTWARE. #import +#import "KeyHandler.h" NS_ASSUME_NONNULL_BEGIN @interface LanguageModelManager : NSObject + (void)loadDataModels; ++ (void)loadDataModel:(InputMode)mode; + (void)loadUserPhrases; + (void)loadUserPhraseReplacement; + (void)setupDataModelValueConverter; diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index c36156b5..2f34cead 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -60,6 +60,16 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo); } ++ (void)loadDataModel:(InputMode)mode +{ + if ([mode isEqualToString:InputModeBopomofo]) { + LTLoadLanguageModelFile(@"data", gLanguageModelMcBopomofo); + } + if ([mode isEqualToString:InputModePlainBopomofo]) { + LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo); + } +} + + (void)loadUserPhrases { gLanguageModelMcBopomofo.loadUserPhrases([[self userPhrasesDataPathMcBopomofo] UTF8String], [[self excludedPhrasesDataPathMcBopomofo] UTF8String]); From 5e19e70c702d6ab36cbdacd422acb75957c6331c Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 08:07:34 +0800 Subject: [PATCH 07/15] Fixes a typo. --- Source/InputMethodController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/InputMethodController.swift b/Source/InputMethodController.swift index aeed9c81..cdadc418 100644 --- a/Source/InputMethodController.swift +++ b/Source/InputMethodController.swift @@ -138,7 +138,7 @@ class McBopomofoInputMethodController: IMKInputController { } } - // MARK: - - IMKServerInput protocol methods + // MARK: - IMKServerInput protocol methods override func recognizedEvents(_ sender: Any!) -> Int { let events: NSEvent.EventTypeMask = [.keyDown, .flagsChanged] From 8a05e4d6f7de78ee70d1f11baac9276c4da27f19 Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 08:18:45 +0800 Subject: [PATCH 08/15] Makes variables in LanguageModelManager static. --- Source/LanguageModelManager.mm | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 2f34cead..87b46cf9 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -34,16 +34,15 @@ using namespace McBopomofo; static const int kUserOverrideModelCapacity = 500; static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. -McBopomofoLM gLanguageModelMcBopomofo; -McBopomofoLM gLanguageModelPlainBopomofo; -UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); - -NSString *const kUserDataTemplateName = @"template-data"; -NSString *const kExcludedPhrasesMcBopomofoTemplateName = @"template-exclude-phrases"; -NSString *const kExcludedPhrasesPlainBopomofoTemplateName = @"template-exclude-phrases-plain-bpmf"; -NSString *const kPhraseReplacementTemplateName = @"template-phrases-replacement"; -NSString *const kTemplateExtension = @".txt"; +static McBopomofoLM gLanguageModelMcBopomofo; +static McBopomofoLM gLanguageModelPlainBopomofo; +static UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); +static NSString *const kUserDataTemplateName = @"template-data"; +static NSString *const kExcludedPhrasesMcBopomofoTemplateName = @"template-exclude-phrases"; +static NSString *const kExcludedPhrasesPlainBopomofoTemplateName = @"template-exclude-phrases-plain-bpmf"; +static NSString *const kPhraseReplacementTemplateName = @"template-phrases-replacement"; +static NSString *const kTemplateExtension = @".txt"; @implementation LanguageModelManager From 5ba7365cd39e38c6dde46edc93dfc0261b57d9f8 Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 08:26:32 +0800 Subject: [PATCH 09/15] Fixes typos. --- Source/Engine/McBopomofoLM.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/McBopomofoLM.h b/Source/Engine/McBopomofoLM.h index 31bcb0f2..b70723f9 100644 --- a/Source/Engine/McBopomofoLM.h +++ b/Source/Engine/McBopomofoLM.h @@ -37,8 +37,8 @@ using namespace Formosa::Gramambular; /// McBopomofoLM is a facade for managing a set of models including /// the input method language model, user phrases and excluded phrases. /// -/// It is the primary model class that the input controller and grammer builder -/// of McBopomofo talk to. When the grammer builder starts to build a sentense +/// It is the primary model class that the input controller and grammar builder +/// of McBopomofo talks to. When the grammar builder starts to build a sentence /// from a series of BPMF readings, it passes the readings to the model to see /// if there are valid unigrams, and use returned unigrams to produce the final /// results. From 4681465a6889454e07da5accbef028f87ee1ed84 Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 08:53:59 +0800 Subject: [PATCH 10/15] Fixes a typo. --- Source/InputMethodController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/InputMethodController.swift b/Source/InputMethodController.swift index cdadc418..6308fc63 100644 --- a/Source/InputMethodController.swift +++ b/Source/InputMethodController.swift @@ -119,7 +119,7 @@ class McBopomofoInputMethodController: IMKInputController { (NSApp.delegate as? AppDelegate)?.checkForUpdate() } - override func deactivateServer(_ sender: Any!) { + override func deactivateServer(_ client: Any!) { keyHandler.clear() self.handle(state: .Empty(), client: client) self.handle(state: .Deactivated(), client: client) From ae9b524caa8b82afa50cd6c5be40b088b3db18f0 Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 09:28:26 +0800 Subject: [PATCH 11/15] Updates the descriptions of the states. --- Source/InputState.swift | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Source/InputState.swift b/Source/InputState.swift index cf198bdc..0338c995 100644 --- a/Source/InputState.swift +++ b/Source/InputState.swift @@ -60,7 +60,7 @@ class InputState: NSObject { @objc (InputStateDeactivated) class Deactivated: InputState { override var description: String { - "" + "" } } @@ -70,8 +70,11 @@ class InputState: NSObject { @objc var composingBuffer: String { "" } - } + override var description: String { + "" + } + } /// Represents that the composing buffer is empty. @objc (InputStateEmptyIgnoringPreviousState) @@ -79,6 +82,9 @@ class InputState: NSObject { @objc var composingBuffer: String { "" } + override var description: String { + "" + } } /// Represents that the input controller is committing text into client app. @@ -92,7 +98,7 @@ class InputState: NSObject { } override var description: String { - "" + "" } } /// Represents that the composing buffer is not empty. @@ -107,7 +113,7 @@ class InputState: NSObject { } override var description: String { - "" + "" } } @@ -131,7 +137,7 @@ class InputState: NSObject { } override var description: String { - "" + "" } } @@ -196,7 +202,7 @@ class InputState: NSObject { } override var description: String { - "" + "" } @objc func convertToInputting() -> Inputting { @@ -238,7 +244,7 @@ class InputState: NSObject { } override var description: String { - "" + "" } } From 0b7fa06d0bb5b655b56cfd2bbf7aad658506c0cf Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 20:06:57 +0800 Subject: [PATCH 12/15] Fixes test cases. --- McBopomofoTests/KeyHandlerBopomofoTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/McBopomofoTests/KeyHandlerBopomofoTests.swift b/McBopomofoTests/KeyHandlerBopomofoTests.swift index da7f6baa..e1ec55a8 100644 --- a/McBopomofoTests/KeyHandlerBopomofoTests.swift +++ b/McBopomofoTests/KeyHandlerBopomofoTests.swift @@ -12,7 +12,7 @@ class KeyHandlerBopomofoTests: XCTestCase { override func setUpWithError() throws { LanguageModelManager.loadDataModels() handler = KeyHandler() - handler.inputMode = kBopomofoModeIdentifier + handler.inputMode = .bopomofo } override func tearDownWithError() throws { From fe67daceee8faa5930becab80e7d2df3acb43725 Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 20:18:43 +0800 Subject: [PATCH 13/15] Modifies the interface of LanguageModelManager to note that some methods are for testing only. --- Source/LanguageModelManager.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index 654bee08..ad75814d 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -28,7 +28,6 @@ NS_ASSUME_NONNULL_BEGIN @interface LanguageModelManager : NSObject -+ (void)loadDataModels; + (void)loadDataModel:(InputMode)mode; + (void)loadUserPhrases; + (void)loadUserPhraseReplacement; @@ -45,4 +44,9 @@ NS_ASSUME_NONNULL_BEGIN @end +/// The following methods are merely for testing. +@interface LanguageModelManager () ++ (void)loadDataModels; +@end + NS_ASSUME_NONNULL_END From 8ba4b9dfdf8a09560aebb48a550278db2395cd83 Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 20:27:33 +0800 Subject: [PATCH 14/15] Prevents loading data models repeatedly. --- Source/Engine/McBopomofoLM.cpp | 5 +++++ Source/Engine/McBopomofoLM.h | 2 ++ Source/Engine/ParselessLM.cpp | 8 ++++++++ Source/Engine/ParselessLM.h | 1 + Source/LanguageModelManager.mm | 16 ++++++++++++---- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Source/Engine/McBopomofoLM.cpp b/Source/Engine/McBopomofoLM.cpp index 8c32d0d4..6e5cb650 100644 --- a/Source/Engine/McBopomofoLM.cpp +++ b/Source/Engine/McBopomofoLM.cpp @@ -39,6 +39,11 @@ McBopomofoLM::~McBopomofoLM() m_phraseReplacement.close(); } +bool McBopomofoLM::isDataModelLoaded() +{ + return m_languageModel.isLoaded(); +} + void McBopomofoLM::loadLanguageModel(const char* languageModelDataPath) { if (languageModelDataPath) { diff --git a/Source/Engine/McBopomofoLM.h b/Source/Engine/McBopomofoLM.h index b70723f9..1636c55d 100644 --- a/Source/Engine/McBopomofoLM.h +++ b/Source/Engine/McBopomofoLM.h @@ -64,6 +64,8 @@ public: /// Asks to load the primary language model a the given path. /// @param languageModelPath Thw path of the language model. void loadLanguageModel(const char* languageModelPath); + /// If the data model is already loaded. + bool isDataModelLoaded(); /// Asks to load the user phrases and excluded phrases at the given path. /// @param userPhrasesPath The path of user phrases. /// @param excludedPhrasesPath The path of excluded phrases. diff --git a/Source/Engine/ParselessLM.cpp b/Source/Engine/ParselessLM.cpp index c0de5c00..2efe9701 100644 --- a/Source/Engine/ParselessLM.cpp +++ b/Source/Engine/ParselessLM.cpp @@ -32,6 +32,14 @@ McBopomofo::ParselessLM::~ParselessLM() { close(); } +bool McBopomofo::ParselessLM::isLoaded() +{ + if (data_) { + return true; + } + return false; +} + bool McBopomofo::ParselessLM::open(const std::string_view& path) { if (data_) { diff --git a/Source/Engine/ParselessLM.h b/Source/Engine/ParselessLM.h index 8d2c0b88..e3b4632a 100644 --- a/Source/Engine/ParselessLM.h +++ b/Source/Engine/ParselessLM.h @@ -37,6 +37,7 @@ class ParselessLM : public Formosa::Gramambular::LanguageModel { public: ~ParselessLM() override; + bool isLoaded(); bool open(const std::string_view& path); void close(); diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 87b46cf9..06c11c12 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -55,17 +55,25 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo + (void)loadDataModels { - LTLoadLanguageModelFile(@"data", gLanguageModelMcBopomofo); - LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo); + if (!gLanguageModelMcBopomofo.isDataModelLoaded()) { + LTLoadLanguageModelFile(@"data", gLanguageModelMcBopomofo); + } + if (!gLanguageModelPlainBopomofo.isDataModelLoaded()) { + LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo); + } } + (void)loadDataModel:(InputMode)mode { if ([mode isEqualToString:InputModeBopomofo]) { - LTLoadLanguageModelFile(@"data", gLanguageModelMcBopomofo); + if (!gLanguageModelMcBopomofo.isDataModelLoaded()) { + LTLoadLanguageModelFile(@"data", gLanguageModelMcBopomofo); + } } if ([mode isEqualToString:InputModePlainBopomofo]) { - LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo); + if (!gLanguageModelPlainBopomofo.isDataModelLoaded()) { + LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo); + } } } From c59560dba28e14deb0c024e941704e844867816c Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 30 Jan 2022 20:37:05 +0800 Subject: [PATCH 15/15] Fixes the behavior to use ESC to clear the input buffer area. --- Source/KeyHandler.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/KeyHandler.mm b/Source/KeyHandler.mm index 478753ea..38f02774 100644 --- a/Source/KeyHandler.mm +++ b/Source/KeyHandler.mm @@ -498,7 +498,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot // if the option is enabled, we clear everything including the composing // buffer, walked nodes and the reading. [self clear]; - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); } else { // if reading is not empty, we cancel the reading; Apple's built-in