From 682d99c2cb8a542c511bd34dc85ef398923df517 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Tue, 26 Sep 2023 10:48:43 +0700 Subject: [PATCH] context simplification, legacy app determination integrated into TextCompatibilityCheck --- mac/Keyman4Mac/Keyman4Mac/AppDelegate.m | 2 +- mac/Keyman4MacIM/Keyman4MacIM/KMContext.m | 14 +- .../Keyman4MacIM/KMCoreActionHandler.m | 23 +-- .../Keyman4MacIM/KMInputMethodAppDelegate.h | 1 - .../Keyman4MacIM/KMInputMethodAppDelegate.m | 13 +- .../Keyman4MacIM/KMInputMethodEventHandler.m | 79 +-------- mac/Keyman4MacIM/Keyman4MacIM/KeySender.h | 2 +- mac/Keyman4MacIM/Keyman4MacIM/KeySender.m | 2 +- .../Keyman4MacIM/TextCompatibilityCheck.h | 3 +- .../Keyman4MacIM/TextCompatibilityCheck.m | 150 ++++++++++++++++-- .../KeymanTests/InputMethodTests.m | 20 ++- .../KeymanTests/TestAppDelegate.m | 4 +- .../KeymanEngine4Mac/CoreWrapper/CoreHelper.m | 8 - .../CoreWrapper/CoreWrapper.h | 6 +- .../CoreWrapper/CoreWrapper.m | 36 +++-- .../KeymanEngine4Mac/KME/KMEngine.h | 7 +- .../KeymanEngine4Mac/KME/KMEngine.m | 44 +---- .../KeymanEngine4MacTests/KMEngineTests.m | 66 ++++---- 18 files changed, 249 insertions(+), 231 deletions(-) diff --git a/mac/Keyman4Mac/Keyman4Mac/AppDelegate.m b/mac/Keyman4Mac/Keyman4Mac/AppDelegate.m index c266dfebba..3e7c3f7f0c 100644 --- a/mac/Keyman4Mac/Keyman4Mac/AppDelegate.m +++ b/mac/Keyman4Mac/Keyman4Mac/AppDelegate.m @@ -116,7 +116,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef if (type == NX_KEYDOWN) { // Key down event NSEvent *mEvent = [NSEvent eventWithCGEvent:event]; - KMEngine *kme = [[KMEngine alloc] initWithKMX:kmx contextBuffer:contextBuffer]; + KMEngine *kme = [[KMEngine alloc] initWithKMX:kmx context:contextBuffer]; NSArray *actions = [kme processEvent:mEvent]; //if (debugMode) NSLog(@"%@", actions); diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMContext.m b/mac/Keyman4MacIM/Keyman4MacIM/KMContext.m index c13c3ff4ea..c0b05f9206 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMContext.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMContext.m @@ -38,13 +38,13 @@ NSUInteger const MAXIMUM_CONTEXT_LENGTH = 80; // KM_KBP_IT_CHAR -(instancetype)initWithString:(NSString*)initialContext { self = [self init]; if (self) { - [_context setString:[self trimToMaximumContext:initialContext]]; + [_context setString:[KMContext trimToMaximumContext:initialContext]]; _invalid = NO; } return self; } --(NSString*)trimToMaximumContext:(NSString*)initialContext { ++(NSString*)trimToMaximumContext:(NSString*)initialContext { NSString *maximumContext = nil; // copy up to MAXIMUM_CONTEXT_LENGTH from the end of the context @@ -59,8 +59,9 @@ NSUInteger const MAXIMUM_CONTEXT_LENGTH = 80; // KM_KBP_IT_CHAR } -(void)resetContext:(NSString*)newContext { - [_context setString:[self trimToMaximumContext:newContext]]; + [_context setString:[KMContext trimToMaximumContext:newContext]]; self.invalid = NO; + NSLog(@" ***CachedContext resetContext, resulting context = %@", _context); } -(NSString*)currentContext { @@ -78,12 +79,14 @@ NSUInteger const MAXIMUM_CONTEXT_LENGTH = 80; // KM_KBP_IT_CHAR -(void)clearContext { _context.string = @""; + NSLog(@" ***CachedContext clearContext, resulting context = %@", _context); } -(void)appendMarker { #define UC_NONCHARACTER 0xFFFF NSString *markerString = [NSString stringWithFormat:@"%C", UC_NONCHARACTER]; [self.context appendString:markerString]; + NSLog(@" ***CachedContext appendMarker, resulting context = %@", _context); } /** @@ -100,6 +103,7 @@ NSUInteger const MAXIMUM_CONTEXT_LENGTH = 80; // KM_KBP_IT_CHAR NSLog(@"KMContext deleteLastUnicodeCharacter, index = %lu, rangeOfLastCharacter = %@\n", characterIndex, NSStringFromRange(characterRange)); [self.context deleteCharactersInRange:characterRange]; } + NSLog(@" ***CachedContext deleteLastCodePoint, resulting context = %@", _context); } -(void)replaceSubstring:(NSString*)newText count:(int)count { @@ -113,10 +117,12 @@ NSUInteger const MAXIMUM_CONTEXT_LENGTH = 80; // KM_KBP_IT_CHAR if (newText.length > 0) { [self addSubtring:newText]; } + NSLog(@" ***CachedContext replaceSubstring, resulting context = %@", _context); } -(void)addSubtring:(NSString*)string { [self.context appendString:string]; + NSLog(@" ***CachedContext addSubtring, resulting context = %@", _context); } -(void)applyAction:(CoreAction*)action keyDownEvent:(nonnull NSEvent *)event { @@ -145,6 +151,7 @@ NSUInteger const MAXIMUM_CONTEXT_LENGTH = 80; // KM_KBP_IT_CHAR NSLog(@"CachedContext applyAction, action not applied to context %@\n", action.typeName.description); break; } + NSLog(@" ***CachedContext applyAction, resulting context = %@", _context); } // TODO: see handleDefaultKeymanEngineActions @@ -189,6 +196,7 @@ NSUInteger const MAXIMUM_CONTEXT_LENGTH = 80; // KM_KBP_IT_CHAR } break; } + NSLog(@" ***CachedContext applyUnhandledEvent, resulting context = %@", _context); } @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMCoreActionHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMCoreActionHandler.m index de9d36b002..db8c744392 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMCoreActionHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMCoreActionHandler.m @@ -129,42 +129,24 @@ typedef enum {BackspacesOnly, switch (self.pattern) { case CharactersOnly: result = [self buildResultForCharactersOnly]; - //handledEvent = [self applyCharacterActions:0]; break; - // TODO: create separate pattern for single backspace? and for EmitKeystroke action? case BackspacesOnly: if ([self isSinglePassThroughBackspace]) { result = [self buildResultForSinglePassThroughBackspaceNoText]; } else { result = [self buildResultForMultipleBackspacesNoText]; - //handledEvent = [self applyIsolatedBackspaceActions:self.backspaceCount]; } break; case BackspaceBeforeCharacter: result = [self buildResultForBackspacesBeforeText]; - //handledEvent = [self applyCharacterActions:self.backspaceCount]; break; case None: result = [self buildResultForNoCharactersOrBackspaces]; break; default: - NSLog(@"Unimplemented pattern***"); + NSLog(@"KMCoreActionHandler handleActions(), Unimplemented pattern***"); } - // TODO: delete - /** - * this event is passed through and will be sent to the client - * but we need to update the context for it -- no, actually we don't: - * the context is updated for every action, and we should still get an action - * for a backspace, even if the event is unhandled by Core - * if there is a need for this, it would be while processing the Emit Keystroke action - */ - /* - if(!result.handledEvent) { - [self.context applyUnhandledEvent:self.event]; - } - */ - return result; } @@ -185,7 +167,7 @@ typedef enum {BackspacesOnly, } /** - * For multiple backspaces with no text, new events must be generated for each backspace + * Multiple backspaces with no text will be handled by generating events */ -(KMActionHandlerResult*)buildResultForMultipleBackspacesNoText { NSLog(@"buildResultForMultipleBackspacesNoText"); @@ -196,7 +178,6 @@ typedef enum {BackspacesOnly, * For backspaces needed before text, insert with replace is possible, but some clients do not support replace */ -(KMActionHandlerResult*)buildResultForBackspacesBeforeText { - // TODO: make backspaces events if replacement does not work with insertText NSLog(@"buildResultForBackspacesBeforeText"); return [[KMActionHandlerResult alloc] initForActions:self.actions handledEvent:YES backspaceCount:self.backspaceCount textToInsert:[self collectOutputText]]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h index 446fd284cc..a16e688dd6 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h @@ -110,7 +110,6 @@ typedef struct { - (void)postKeyboardEventWithSource: (CGEventSourceRef)source code:(CGKeyCode) virtualKey postCallback:(PostEventCallback)postEvent; - (KeymanVersionInfo)versionInfo; - (NSString *)keymanDataPath; -- (NSArray *)legacyAppsUserDefaults; - (void)registerConfigurationWindow:(NSWindowController *)window; @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index d3ccb4f7fa..cc618ad7d3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -39,7 +39,6 @@ NSString *const kKMDeprecatedPersistedOptionsKey = @"KMSavedStoresKey"; NSString *const kKMPersistedOptionsKey = @"KMPersistedOptionsKey"; NSString *const kKMAlwaysShowOSKKey = @"KMAlwaysShowOSKKey"; NSString *const kKMUseVerboseLogging = @"KMUseVerboseLogging"; -NSString *const kKMLegacyApps = @"KMLegacyApps"; NSString *const kKeymanKeyboardDownloadCompletedNotification = @"kKeymanKeyboardDownloadCompletedNotification"; @@ -393,7 +392,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef - (KMEngine *)kme { if (_kme == nil) { //TODO: verify that initial context is correct; this is passed to core - _kme = [[KMEngine alloc] initWithKMX:nil contextBuffer:self.contextBuffer]; + _kme = [[KMEngine alloc] initWithKMX:nil context:self.contextBuffer]; [_kme setDebugMode:self.debugMode]; } @@ -530,14 +529,6 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef return _keymanDataPath; } -/** - * Returns the list of user-default legacy apps - */ -- (NSArray *)legacyAppsUserDefaults { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - return [userData arrayForKey:kKMLegacyApps]; -} - /** * Returns the root folder where keyboards are stored; currently the same * as the keymanDataPath, but may diverge in future versions (possibly a sub-folder) @@ -773,7 +764,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef _contextBuffer = [contextBuffer mutableCopy]; if (_contextBuffer.length) [_contextBuffer replaceOccurrencesOfString:@"\0" withString:[NSString nullChar] options:0 range:NSMakeRange(0, 1)]; - [self.kme setContextBuffer:self.contextBuffer]; + [self.kme setCoreContext:self.contextBuffer]; } - (void)awakeFromNib { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index f8a313c800..23b25b28f3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -59,72 +59,6 @@ NSRange _previousSelRange; return self; } -/** - * Checks if the client app requires legacy input mode, first by checking the user defaults, if they exist, - * then, by our hard-coded list. - */ -- (BOOL)isClientAppLegacy:(NSString *)clientAppId { - NSArray *legacyAppsUserDefaults = [self.AppDelegate legacyAppsUserDefaults]; - - BOOL result = NO; - - if(legacyAppsUserDefaults != nil) { - result = [self isClientAppLegacy:clientAppId fromArray:legacyAppsUserDefaults]; - } - - if(!result) { - // TODO: Pages and Keynote (and possibly lots of other undiscovered apps that are otherwise compliant - // with Apple's IM framework) have a problem in that if the user selects a different font (or other - // formatting) and then types a sequence that causes characters to be added to the document and then - // subsequently replaced, the replacement causes the formatting decision to be forgotten. This can be - // "fixed" by treating them as legacy apps, but it causes other problems. - result = ([clientAppId isEqual: @"com.github.atom"] || - [clientAppId isEqual: @"com.collabora.libreoffice-free"] || - [clientAppId isEqual: @"org.libreoffice.script"] || - [clientAppId isEqual: @"com.axosoft.gitkraken"] || - [clientAppId isEqual: @"org.sil.app.builder.scripture.ScriptureAppBuilder"] || - [clientAppId isEqual: @"org.sil.app.builder.reading.ReadingAppBuilder"] || - [clientAppId isEqual: @"org.sil.app.builder.dictionary.DictionaryAppBuilder"] || - //[clientAppId isEqual: @"com.microsoft.Word"] || // 2020-11-24[mcd]: Appears to work well in Word 16.43, disable legacy by default - [clientAppId isEqual: @"org.openoffice.script"] || - [clientAppId isEqual: @"com.adobe.illustrator"] || - [clientAppId isEqual: @"com.adobe.InDesign"] || - [clientAppId isEqual: @"com.adobe.Photoshop"] || - [clientAppId isEqual: @"com.adobe.AfterEffects"] || - [clientAppId isEqual: @"com.microsoft.VSCode"] || - [clientAppId isEqual: @"com.google.Chrome"] || - [clientAppId hasPrefix: @"net.java"] || - [clientAppId isEqual: @"com.Keyman.test.legacyInput"] - /*||[clientAppId isEqual: @"ro.sync.exml.Oxygen"] - Oxygen has worse problems */ - ); - } - - return result; -} - -/** - * Checks user defaults array for a list of possible regexes to match a client app id - */ -- (BOOL)isClientAppLegacy:(NSString *)clientAppId fromArray:(NSArray *)legacyApps { - for(id legacyApp in legacyApps) { - if(![legacyApp isKindOfClass:[NSString class]]) { - NSLog(@"isClientAppLegacy:fromArray: LegacyApps user defaults array should contain only strings"); - } else { - NSError *error = nil; - NSRange range = NSMakeRange(0, clientAppId.length); - - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: (NSString *) legacyApp options: 0 error: &error]; - NSArray *matchesArray = [regex matchesInString:clientAppId options:0 range:range]; - if(matchesArray.count>0) { - NSLog(@"isClientAppLegacy: found match for legacy app %@: ", clientAppId); - return YES; - } - } - } - - return NO; -} - // This is the public initializer. - (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender { _textCompatibility = [[TextCompatibilityCheck alloc]initWithClient:sender applicationId:clientAppId]; @@ -136,7 +70,7 @@ NSRange _previousSelRange; _generatedBackspaceCount = 0; - BOOL legacy = [self isClientAppLegacy:clientAppId]; + //BOOL legacy = [self isClientAppLegacy:clientAppId]; // In Xcode, if Keyman is the active IM and is in "debugMode" and "English plus Spanish" is // the current keyboard and you type "Sentry force now", it will force a simulated crash to @@ -148,10 +82,12 @@ NSRange _previousSelRange; else _easterEggForSentry = nil; + //TODO: move to TextCompatibilityCheck // For the Atom editor, this isn't really true (the context CAN change unexpectedly), but we can't get // the context, so we pretend/hope it won't. BOOL selectionCanChangeUnexpectedly = (![clientAppId isEqual: @"com.github.atom"]); - return [self initWithLegacyMode:legacy clientSelectionCanChangeUnexpectedly:selectionCanChangeUnexpectedly]; + // TODO: this equates 'legacyMode' with being forced to send events + return [self initWithLegacyMode:self.textCompatibility.mustBackspaceUsingEvents clientSelectionCanChangeUnexpectedly:selectionCanChangeUnexpectedly]; } - (void)switchToLegacyMode { @@ -967,7 +903,7 @@ NSRange _previousSelRange; //MARK: Core-related key processing // replacement handleEvent implementation for core event processing - (BOOL)handleEvent:(NSEvent *)event client:(id)sender { - NSLog(@"SGS handleEvent event = %@", event); + NSLog(@"handleEvent event = %@", event); // mouse movement requires that the context be invalidated @@ -1053,13 +989,14 @@ NSRange _previousSelRange; } } +/* -(void)checkClientTextCompatibility:(id) client { if(!self.textCompatibility) { _textCompatibility = [[TextCompatibilityCheck alloc]initWithClient:client applicationId:self.clientApplicationId]; NSLog(@"KMInputMethodHandler checkClientTextCompatibility: %@", self.textCompatibility); } } - +*/ /* -(BOOL)containsKeymanCoreActions:(NSArray*)actions { BOOL containsCoreActions = NO; @@ -1110,7 +1047,7 @@ NSRange _previousSelRange; NSUInteger realLengthAfter = [_cachedContext.currentContext lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4; - NSLog(@"applyActions, contextBefore = '%@', length = %lu, real length = %lu; contextAfter = '%@', length = %lu, real length = %lu", contextBefore, lengthBefore, realLengthBefore, contextAfter, lengthAfter, realLengthAfter); + NSLog(@"applyKeymanCoreActions, contextBefore = '%@', length = %lu, real length = %lu; contextAfter = '%@', length = %lu, real length = %lu", contextBefore, lengthBefore, realLengthBefore, contextAfter, lengthAfter, realLengthAfter); return result.handledEvent; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KeySender.h b/mac/Keyman4MacIM/Keyman4MacIM/KeySender.h index 59be2a628c..672dbbca04 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KeySender.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KeySender.h @@ -2,7 +2,7 @@ * Keyman is copyright (C) SIL International. MIT License. * * KeySender.h - * KeyTest + * Keyman * * Created by Shawn Schantz on 2023-04-17. * diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m b/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m index 5c7bb81e93..afe260f855 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m @@ -2,7 +2,7 @@ * Keyman is copyright (C) SIL International. MIT License. * * KeySender.m - * KeyTest + * Keyman * * Created by Shawn Schantz on 2023-04-17. * diff --git a/mac/Keyman4MacIM/Keyman4MacIM/TextCompatibilityCheck.h b/mac/Keyman4MacIM/Keyman4MacIM/TextCompatibilityCheck.h index 16d7506028..d87e4873be 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/TextCompatibilityCheck.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/TextCompatibilityCheck.h @@ -2,7 +2,7 @@ * Keyman is copyright (C) SIL International. MIT License. * * TextCompatibilityCheck.h - * KeyTest + * Keyman * * Created by Shawn Schantz on 2023-05-05. * @@ -21,7 +21,6 @@ NS_ASSUME_NONNULL_BEGIN -(BOOL)canInsertText; -(BOOL)canReplaceText; -(BOOL)mustBackspaceUsingEvents; - @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/TextCompatibilityCheck.m b/mac/Keyman4MacIM/Keyman4MacIM/TextCompatibilityCheck.m index 26103357da..df02fd5d6a 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/TextCompatibilityCheck.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/TextCompatibilityCheck.m @@ -2,7 +2,7 @@ * Keyman is copyright (C) SIL International. MIT License. * * TextCompatibilityCheck.m - * KeyTest + * Keyman * * Created by Shawn Schantz on 2023-05-05. * @@ -12,6 +12,8 @@ #import "TextCompatibilityCheck.h" #import +NSString *const kKMLegacyApps = @"KMLegacyApps"; + @interface TextCompatibilityCheck() @property (readonly) id client; @@ -22,7 +24,6 @@ @end - @implementation TextCompatibilityCheck -(instancetype)initWithClient:(id) client applicationId:(NSString *)appId { @@ -47,20 +48,16 @@ return [NSString stringWithFormat:@"hasSelectionAPI: %d, hasReadAPI: %d, hasInse -(BOOL) checkSelectionApi:(id) client applicationId:(NSString *)clientAppId { BOOL workingSelectionApi = NO; - // if the selector exists, then call the API and see if it returns a valid value + workingSelectionApi = [client respondsToSelector:@selector(selectedRange)]; - if ([client respondsToSelector:@selector(selectedRange)]) { + /* + // if the selector exists, then call the API and see if it returns a valid value + if (workingSelectionApi) { NSRange selectionRange = [self.client selectedRange]; NSLog(@"TextCompatibilityCheck checkSelectionApi, location = %lu, length = %lu", selectionRange.location, selectionRange.length); NSRange notFoundRange = NSMakeRange(NSNotFound, NSNotFound); - /* - if (selectionRange.location == 0) { - canGetSelection = NO; - NSLog(@"checkCanGetSelection, selectionRange.location == 0 "); - } else - */ if (NSEqualRanges(selectionRange, notFoundRange)) { workingSelectionApi = YES; // no current selection, but API is working @@ -69,12 +66,39 @@ return [NSString stringWithFormat:@"hasSelectionAPI: %d, hasReadAPI: %d, hasInse workingSelectionApi = YES; } } - - // TODO: integrate with existing legacy code +*/ + + // if the selection API appears to work, it may still be broken - // getting the selection does not work for anything in the legacy app list + // getting the selection does not work for anything in the noncompliant app lists if (workingSelectionApi) { - BOOL isLegacy = ([clientAppId isEqual: @"com.github.atom"] || + workingSelectionApi = ![self containedInNoncompliantAppLists:clientAppId]; + } + + NSLog(@"hasWorkingSelectionApi for app %@: set to %@", clientAppId, workingSelectionApi?@"yes":@"no"); + return workingSelectionApi; +} + +/** + * Checks if the client app is known to be non-complian, first by checking the hard-coded non-compliant app list + * and then by checking the user-managed (via user defaults) non-compliant app list + */ +- (BOOL)containedInNoncompliantAppLists:(NSString *)clientAppId { + BOOL isAppNonCompliant = [self containedInHardCodedNoncompliantAppList:clientAppId]; + if (!isAppNonCompliant) { + isAppNonCompliant = [self containedInUserManagedNoncompliantAppList:clientAppId]; + } + + NSLog(@"containedInNoncompliantAppLists: for app %@: %@", clientAppId, isAppNonCompliant?@"yes":@"no"); + return isAppNonCompliant; +} + +/** Check this hard-coded list to see if the application ID is among those +* that are known to not implement selectionRange correctly. +* This was formerly called the legacy app list, renamed to improve clarity. +*/ +- (BOOL)containedInHardCodedNoncompliantAppList:(NSString *)clientAppId { + BOOL isAppNonCompliant = ([clientAppId isEqual: @"com.github.atom"] || [clientAppId isEqual: @"com.collabora.libreoffice-free"] || [clientAppId isEqual: @"org.libreoffice.script"] || [clientAppId isEqual: @"com.axosoft.gitkraken"] || @@ -93,11 +117,105 @@ return [NSString stringWithFormat:@"hasSelectionAPI: %d, hasReadAPI: %d, hasInse [clientAppId isEqual: @"com.Keyman.test.legacyInput"] /*||[clientAppId isEqual: @"ro.sync.exml.Oxygen"] - Oxygen has worse problems */ ); - workingSelectionApi = !isLegacy; + NSLog(@"containedInHardCodedNoncompliantAppList: for app %@: %@", clientAppId, isAppNonCompliant?@"yes":@"no"); + return isAppNonCompliant; } - return workingSelectionApi; + +/** + * Returns the list of user-default legacy apps + */ +- (NSArray *)legacyAppsUserDefaults { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData arrayForKey:kKMLegacyApps]; } +/** Check this user-managed list to see if the application ID is among those +* that are known to not implement selectionRange correctly. +* This was formerly called the legacy app list, renamed to improve clarity. +*/ +- (BOOL)containedInUserManagedNoncompliantAppList:(NSString *)clientAppId { + BOOL isAppNonCompliant = false; + NSArray *legacyAppsUserDefaults = self.legacyAppsUserDefaults; + + if(legacyAppsUserDefaults != nil) { + isAppNonCompliant = [self isClientAppLegacy:clientAppId fromArray:legacyAppsUserDefaults]; + } + NSLog(@"containedInUserManagedNoncompliantAppList: for app %@: %@", clientAppId, isAppNonCompliant?@"yes":@"no"); + + return isAppNonCompliant; + } + +/** + * Checks if the client app requires legacy input mode, first by checking the user defaults, if they exist, + * then, by our hard-coded list. + */ +/* +- (BOOL)isClientAppLegacy:(NSString *)clientAppId { + BOOL isAppNonCompliant = false; + + NSArray *legacyAppsUserDefaults = self.legacyAppsUserDefaults; + + BOOL result = NO; + + if(legacyAppsUserDefaults != nil) { + result = [self isClientAppLegacy:clientAppId fromArray:legacyAppsUserDefaults]; + } + + if(!result) { + // TODO: Pages and Keynote (and possibly lots of other undiscovered apps that are otherwise compliant + // with Apple's IM framework) have a problem in that if the user selects a different font (or other + // formatting) and then types a sequence that causes characters to be added to the document and then + // subsequently replaced, the replacement causes the formatting decision to be forgotten. This can be + // "fixed" by treating them as legacy apps, but it causes other problems. + result = ([clientAppId isEqual: @"com.github.atom"] || + [clientAppId isEqual: @"com.collabora.libreoffice-free"] || + [clientAppId isEqual: @"org.libreoffice.script"] || + [clientAppId isEqual: @"com.axosoft.gitkraken"] || + [clientAppId isEqual: @"org.sil.app.builder.scripture.ScriptureAppBuilder"] || + [clientAppId isEqual: @"org.sil.app.builder.reading.ReadingAppBuilder"] || + [clientAppId isEqual: @"org.sil.app.builder.dictionary.DictionaryAppBuilder"] || + //[clientAppId isEqual: @"com.microsoft.Word"] || // 2020-11-24[mcd]: Appears to work well in Word 16.43, disable legacy by default + [clientAppId isEqual: @"org.openoffice.script"] || + [clientAppId isEqual: @"com.adobe.illustrator"] || + [clientAppId isEqual: @"com.adobe.InDesign"] || + [clientAppId isEqual: @"com.adobe.Photoshop"] || + [clientAppId isEqual: @"com.adobe.AfterEffects"] || + [clientAppId isEqual: @"com.microsoft.VSCode"] || + [clientAppId isEqual: @"com.google.Chrome"] || + [clientAppId hasPrefix: @"net.java"] || + [clientAppId isEqual: @"com.Keyman.test.legacyInput"] + //||[clientAppId isEqual: @"ro.sync.exml.Oxygen"] - Oxygen has worse problems + ); + } + + return result; +} +*/ + +/** + * Checks array for a list of possible regexes to match a client app id + */ +- (BOOL)isClientAppLegacy:(NSString *)clientAppId fromArray:(NSArray *)legacyApps { + for(id legacyApp in legacyApps) { + if(![legacyApp isKindOfClass:[NSString class]]) { + NSLog(@"isClientAppLegacy:fromArray: LegacyApps user defaults array should contain only strings"); + } else { + NSError *error = nil; + NSRange range = NSMakeRange(0, clientAppId.length); + + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: (NSString *) legacyApp options: 0 error: &error]; + NSArray *matchesArray = [regex matchesInString:clientAppId options:0 range:range]; + if(matchesArray.count>0) { + NSLog(@"isClientAppLegacy: found match for legacy app %@: ", clientAppId); + return YES; + } + } + } + + return NO; +} + + -(BOOL) canGetSelection { return self.hasWorkingSelectionApi; } diff --git a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m index 15f5cadd1f..11599cb220 100644 --- a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m +++ b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m @@ -14,11 +14,19 @@ #import "KMInputMethodEventHandlerProtected.h" #import "LegacyTestClient.h" #import "AppleCompliantTestClient.h" +#import "TextCompatibilityCheck.h" @interface InputMethodTests : XCTestCase @end +// included following interface that we can see and test private methods of TextCompatibilityCheck +@interface TextCompatibilityCheck (Testing) + +- (BOOL)isClientAppLegacy:(NSString *)clientAppId fromArray:(NSArray *)legacyApps; + +@end + @implementation InputMethodTests - (void)setUp { @@ -30,25 +38,25 @@ } - (void)testIsClientAppLegacy_unlistedClientAppId_returnsNo { - KMInputMethodEventHandler * inputMethod = [[KMInputMethodEventHandler alloc] initWithLegacyMode:NO clientSelectionCanChangeUnexpectedly:NO]; id client = [[AppleCompliantTestClient alloc] init]; - NSString *clientAppId = @"com.compliant.app"; + TextCompatibilityCheck *textCompatibility = [[TextCompatibilityCheck alloc]initWithClient:client applicationId:clientAppId]; + NSArray *legacyAppsArray = [NSArray arrayWithObjects:@"com.microsoft.VSCode",@"com.adobe.Photoshop",nil]; - BOOL isLegacy = [inputMethod isClientAppLegacy:clientAppId fromArray:legacyAppsArray]; + BOOL isLegacy = [textCompatibility isClientAppLegacy:clientAppId fromArray:legacyAppsArray]; NSLog(@"isLegacy = %@", isLegacy?@"yes":@"no"); XCTAssert(isLegacy == NO, @"App not expected to be in legacy list"); } - (void)testIsClientAppLegacy_listedClientAppId_returnsYes { - KMInputMethodEventHandler * inputMethod = [[KMInputMethodEventHandler alloc] initWithLegacyMode:NO clientSelectionCanChangeUnexpectedly:NO]; id client = [[AppleCompliantTestClient alloc] init]; - NSString *clientAppId = @"com.microsoft.VSCode"; + TextCompatibilityCheck *textCompatibility = [[TextCompatibilityCheck alloc]initWithClient:client applicationId:clientAppId]; + NSArray *legacyAppsArray = [NSArray arrayWithObjects:@"com.adobe.Photoshop",@"com.microsoft.VSCode",nil]; - BOOL isLegacy = [inputMethod isClientAppLegacy:clientAppId fromArray:legacyAppsArray]; + BOOL isLegacy = [textCompatibility isClientAppLegacy:clientAppId fromArray:legacyAppsArray]; NSLog(@"isLegacy = %@", isLegacy?@"yes":@"no"); XCTAssert(isLegacy == YES, @"App expected to be in legacy list"); } diff --git a/mac/Keyman4MacIM/KeymanTests/TestAppDelegate.m b/mac/Keyman4MacIM/KeymanTests/TestAppDelegate.m index 67f2774335..64564a930e 100644 --- a/mac/Keyman4MacIM/KeymanTests/TestAppDelegate.m +++ b/mac/Keyman4MacIM/KeymanTests/TestAppDelegate.m @@ -16,7 +16,7 @@ - (KMEngine *)kme { if (_kme == nil) { - _kme = [[KMEngine alloc] initWithKMX:nil contextBuffer:self.contextBuffer]; + _kme = [[KMEngine alloc] initWithKMX:nil context:self.contextBuffer]; [_kme setDebugMode:self.debugMode]; } @@ -40,7 +40,7 @@ _contextBuffer = [contextBuffer mutableCopy]; if (_contextBuffer.length) [_contextBuffer replaceOccurrencesOfString:@"\0" withString:[NSString nullChar] options:0 range:NSMakeRange(0, 1)]; - [self.kme setContextBuffer:self.contextBuffer]; + [self.kme setCoreContext:self.contextBuffer]; } - (BOOL)debugMode { diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m index f13efd73ce..1d8061c56e 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreHelper.m @@ -81,34 +81,26 @@ UInt32 VirtualKeyMap[VIRTUAL_KEY_ARRAY_SIZE]; return optimizedActionArray; } -// TODO: remove excessive debug statements above and below -(UInt32)macToKeymanModifier:(NSEventModifierFlags)modifiers { UInt32 keymanModifiers = 0; if ([self isShiftKey:modifiers]) { keymanModifiers |= KM_KBP_MODIFIER_SHIFT; - NSLog(@"macToKeymanModifier setting shift bit"); } if([self isLeftOptionKey:modifiers]) { keymanModifiers |= KM_KBP_MODIFIER_LALT; - NSLog(@"macToKeymanModifier setting left alt bit"); } else if([self isRightOptionKey:modifiers]) { keymanModifiers |= KM_KBP_MODIFIER_RALT; - NSLog(@"macToKeymanModifier setting right alt bit"); } else if ([self isOptionKey:modifiers]) { keymanModifiers |= KM_KBP_MODIFIER_ALT; - NSLog(@"macToKeymanModifier setting alt bit"); } if ([self isLeftControlKey:modifiers]) { keymanModifiers |= KM_KBP_MODIFIER_LCTRL; - NSLog(@"macToKeymanModifier setting left control bit"); } else if ([self isRightControlKey:modifiers]) { keymanModifiers |= KM_KBP_MODIFIER_RCTRL; - NSLog(@"macToKeymanModifier setting right control bit"); } else if ([self isControlKey:modifiers]) { keymanModifiers |= KM_KBP_MODIFIER_CTRL; - NSLog(@"macToKeymanModifier setting control bit"); } if ([self isCapsLockKey:modifiers]) { diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreWrapper.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreWrapper.h index 5cf4099b49..716bb571b4 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreWrapper.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreWrapper.h @@ -16,9 +16,9 @@ NS_ASSUME_NONNULL_BEGIN @interface CoreWrapper : NSObject -@property (strong, nonatomic, readonly) NSString * keyboardId; -@property (strong, nonatomic, readonly) NSString * keyboardVersion; -@property (strong, nonatomic) NSString * context; +@property (strong, nonatomic, readonly) NSString *keyboardId; +@property (strong, nonatomic, readonly) NSString *keyboardVersion; +@property (strong, nonatomic) NSString *context; -(instancetype)initWithHelper:(CoreHelper*)helper kmxFilePath:(nullable NSString*)path; -(BOOL)setOptionsForCore: (NSString *) key value:(NSString *) value; diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreWrapper.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreWrapper.m index 8c8891ed8d..8c43f9d0ca 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreWrapper.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/CoreWrapper/CoreWrapper.m @@ -47,6 +47,9 @@ [self setContextUsingCore:newValue]; } +-(void)clearContext { + [self clearContextUsingCore]; +} -(void)changeKeyboardWithKmxFilePath:(NSString*) path { if (path != nil) { @try { @@ -299,19 +302,28 @@ return immutableString; } --(void)setContextUsingCore:(NSString*)context { - char const *coreString = [context cStringUsingEncoding:NSUTF8StringEncoding]; - km_kbp_context_item *contextItemArray; - - // create array of context items - km_kbp_status result = km_kbp_context_items_from_utf8(coreString, &contextItemArray); - NSLog(@"km_kbp_context_items_from_utf8, result=%i", result); - - // set the context in core using the array +-(void)clearContextUsingCore { km_kbp_context * coreContext = km_kbp_state_context(self.keyboardState); - km_kbp_context_set(coreContext, contextItemArray); - // dispose - km_kbp_context_items_dispose(contextItemArray); + km_kbp_context_clear(coreContext); +} + +-(void)setContextUsingCore:(NSString*)context { + if (context.length == 0) { + [self clearContextUsingCore]; + } else { + char const *coreString = [context cStringUsingEncoding:NSUTF8StringEncoding]; + km_kbp_context_item *contextItemArray; + + // create array of context items + km_kbp_status result = km_kbp_context_items_from_utf8(coreString, &contextItemArray); + NSLog(@"km_kbp_context_items_from_utf8, result=%i", result); + + // set the context in core using the array + km_kbp_context * coreContext = km_kbp_state_context(self.keyboardState); + km_kbp_context_set(coreContext, contextItemArray); + // dispose + km_kbp_context_items_dispose(contextItemArray); + } } //TODO: create and save as static diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.h index db4de2b5a7..6b9922647a 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.h @@ -19,11 +19,16 @@ @property (weak, nonatomic) KMXFile *kmx; @property (assign, nonatomic) BOOL debugMode; -- (id)initWithKMX:(KMXFile *)kmx contextBuffer:(NSString *)ctxBuf; +- (id)initWithKMX:(KMXFile *)kmx context:(NSString *)ctxBuf; +- (NSString *)getCoreContext; +- (void)clearCoreContext; - (void)setCoreContext:(NSString *)context; + //TODO: remove additional context methods +/* - (void)setContextBuffer:(NSString *)ctxBuf; - (NSString *)contextBuffer; + */ - (void)setCoreOptions:(NSString *)key withValue:(NSString *)value; - (NSArray *)processEvent:(NSEvent *)event; - (void)setUseVerboseLogging:(BOOL)useVerboseLogging; diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m index 7481cd0a3f..ab511cf217 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m @@ -20,7 +20,6 @@ //DWORD VKMap[0x80]; @interface KMEngine () -@property (strong, nonatomic) NSMutableString *tmpCtxBuf; @property (readonly) CoreHelper *coreHelper; @property (nonatomic, retain) CoreWrapper * keymanCore; @end @@ -31,7 +30,7 @@ NSMutableString* _easterEggForSentry = nil; const NSString* kEasterEggText = @"Sentrycrash#KME"; const NSString* kEasterEggKmxName = @"EnglishSpanish.kmx"; -- (id)initWithKMX:(KMXFile *)kmx contextBuffer:(NSString *)ctxBuf { +- (id)initWithKMX:(KMXFile *)kmx context:(NSString *)contextString { self = [super init]; if (self) { #ifdef DEBUG @@ -44,14 +43,8 @@ const NSString* kEasterEggKmxName = @"EnglishSpanish.kmx"; if (kmx) { [self loadCoreWrapperFromKmxFile:self.kmx.filePath]; - [self.keymanCore setContext:ctxBuf]; + [self.keymanCore setContext:contextString]; } - - _tmpCtxBuf = [[NSMutableString alloc] initWithString:ctxBuf]; - [_tmpCtxBuf removeAllNullChars]; - if (_tmpCtxBuf.length) - [_tmpCtxBuf replaceOccurrencesOfString:@"\0" withString:@"" options:0 range:NSMakeRange(0, 1)]; - //[self setVKMapping]; } return self; @@ -97,36 +90,15 @@ const NSString* kEasterEggKmxName = @"EnglishSpanish.kmx"; NSLog(@"KME - Turning verbose logging off"); } +- (NSString *)getCoreContext { + return self.keymanCore.context; +} + - (void)setCoreContext:(NSString *)context { [self.keymanCore setContext:context]; } -- (void)setContextBuffer:(NSString *)ctxBuf { - _tmpCtxBuf = [[NSMutableString alloc] initWithString:ctxBuf]; - [_tmpCtxBuf removeAllNullChars]; - if (_tmpCtxBuf.length) - [_tmpCtxBuf replaceOccurrencesOfString:@"\0" withString:@"" options:0 range:NSMakeRange(0, 1)]; -} - -- (NSString *)contextBuffer { - return [NSString stringWithString:self.tmpCtxBuf]; -} - -- (NSMutableString *)tmpCtxBuf { - if (_tmpCtxBuf == nil) { - _tmpCtxBuf = [[NSMutableString alloc] initWithString:@""]; - } - - return _tmpCtxBuf; -} - - (void)setCoreOptions:(NSString *)key withValue:(NSString *)value { - /* - KMCompStore *store = [self.kmx.store objectAtIndex:storeID]; - KMCompStore *storeSaved = [self.kmx.storeSaved objectAtIndex:storeID]; - store.string = [[NSString alloc] initWithString:value]; - storeSaved.string = [[NSString alloc] initWithString:value]; - */ BOOL success = [self.keymanCore setOptionsForCore:key value:value]; NSLog(@"setCoreOptions for key: %@, value: %@ succeeded = %@", key, value, success ? @"YES" : @"NO"); } @@ -144,11 +116,7 @@ const NSString* kEasterEggKmxName = @"EnglishSpanish.kmx"; if ([coreActions count] == 0) { return nil; } else { - // update context from core - [self.tmpCtxBuf setString:self.keymanCore.context]; return coreActions; - // TODO: integrate the different contexts - // -- probably ignore/trash tmpCtxBuf because we update local context using actions } } diff --git a/mac/KeymanEngine4Mac/KeymanEngine4MacTests/KMEngineTests.m b/mac/KeymanEngine4Mac/KeymanEngine4MacTests/KMEngineTests.m index 45d287adfb..867582e8cb 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4MacTests/KMEngineTests.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4MacTests/KMEngineTests.m @@ -31,7 +31,7 @@ NSString * names[nCombinations]; } - (void)testinitWithKMX_NilKmx_ProcessEventReturnsNil { - KMEngine *engine = [[KMEngine alloc] initWithKMX:nil contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:nil context:@""]; NSEvent *event = [[NSEvent alloc] init]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions == nil, @"Expected processEvent to return nil for nil kmx"); @@ -39,28 +39,28 @@ NSString * names[nCombinations]; - (void)testinitWithKMX_ValidKmxEmptyContext_InitializedWithEmptyContext { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileTestMacEngine]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; XCTAssert(engine != nil, @"Expected non-nil engine"); - XCTAssert(engine.contextBuffer.length == 0, @"Expected empty context buffer"); + XCTAssert(engine.getCoreContext.length == 0, @"Expected empty context buffer"); } - (void)testinitWithKMX_ValidKmxNonEmptyContext_InitializedWithContext { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileTestMacEngine]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@"abc"]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@"abc"]; XCTAssert(engine != nil, @"Expected non-nil engine"); - XCTAssert([engine.contextBuffer isEqualToString:@"abc"], @"Expected 'abc' in context buffer"); + XCTAssert([engine.getCoreContext isEqualToString:@"abc"], @"Expected 'abc' in context buffer"); } - (void)testsetContextBuffer_NonEmptyContext_InitializedWithContext { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileTestMacEngine]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; - [engine setContextBuffer:@"xyz"]; - XCTAssert([engine.contextBuffer isEqualToString:@"xyz"], @"Expected 'xyz' in context buffer"); + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; + [engine setCoreContext:@"xyz"]; + XCTAssert([engine.getCoreContext isEqualToString:@"xyz"], @"Expected 'xyz' in context buffer"); } - (void)testprocessEvent_eventForCommandKey_ReturnsNilActionsArray { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileTestMacEngine]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:NSEventModifierFlagCommand timestamp:0 windowNumber:0 context:nil characters:@"a" charactersIgnoringModifiers:@"a" isARepeat:NO keyCode:kVK_ANSI_A]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions == nil, @"Expected nil array of actions"); @@ -68,7 +68,7 @@ NSString * names[nCombinations]; - (void)testprocessEvent_eventWithoutKeycode_ReturnsNilActionsArray { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileTestMacEngine]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; NSEvent *event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved location:NSMakePoint(29, 21) modifierFlags:NSEventModifierFlagShift timestamp:0 windowNumber:0 context:nil eventNumber:23 clickCount:0 pressure:0]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions == nil, @"Expected nil array of actions"); @@ -77,7 +77,7 @@ NSString * names[nCombinations]; // TODO: - (void)testprocessEvent_eventForUnmappedKey_ReturnsNoActions { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileTestMacEngine]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:@"z" charactersIgnoringModifiers:@"z" isARepeat:NO keyCode:kVK_ANSI_Z]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions == nil, @"Expected nil array of actions"); @@ -85,7 +85,7 @@ NSString * names[nCombinations]; - (void)testprocessEvent_eventForLowercaseA_ReturnsCharacterActionWithExpectedCharacterBasedOnKmx { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileTestMacEngine]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:@"a" charactersIgnoringModifiers:@"a" isARepeat:NO keyCode:kVK_ANSI_A]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions.count == 1, @"Expected 1 action"); @@ -127,14 +127,14 @@ NSString * names[nCombinations]; XCTAssert([actionType isEqualToString:Q_STR], @"Expected Q_STR action"); NSString *output = [action objectForKey:actionType]; XCTAssert([output isEqualToString:expectedOutput], @"Output incorrect"); - [engine setContextBuffer:@""]; + [engine clearContext]; } } */ - (void)testprocessEvent_eventsForOpenCurlyBraceWithCipherMusicKmx_ReturnsCharacterActionForStartSlide { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForCipherMusicTests]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; UTF32Char expectedUtf32Char = 0x1D177; NSString * expectedStartSlideSurrogatePair = [[NSString alloc] initWithBytes:&expectedUtf32Char length:4 encoding:NSUTF32LittleEndianStringEncoding]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:NSEventModifierFlagShift timestamp:0 windowNumber:0 context:nil characters:@"{" charactersIgnoringModifiers:@"[" isARepeat:NO keyCode:kVK_ANSI_LeftBracket]; @@ -161,7 +161,7 @@ NSString * names[nCombinations]; CoreAction *action = actions[0]; XCTAssert([action isCharacter], @"Expected CharacterAction"); XCTAssert([action.content isEqualToString:numeral], @"Output incorrect"); - [engine setContextBuffer:@""]; + [engine clearContext]; } } */ @@ -169,7 +169,7 @@ NSString * names[nCombinations]; // TODO: fails with core, returns CharacterAction - (void)testprocessEvent_eventForShiftNumeralsWithoutRulesInCipherMusicKmx_ReturnsNoAction { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForCipherMusicTests]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; for (int i = 0; i <= 9; i++) { if (i == 6) @@ -212,7 +212,7 @@ NSString * names[nCombinations]; // TODO: fails with core, returns CharacterAction - (void)testprocessEvent_eventForPeriodWithCipherMusicKmx_ReturnsNoAction { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForCipherMusicTests]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:@"." charactersIgnoringModifiers:@"." isARepeat:NO keyCode:kVK_ANSI_Period]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions.count == 0, @"Expected no actions"); @@ -221,7 +221,7 @@ NSString * names[nCombinations]; // TODO: fails with core, returns CharacterAction - (void)testprocessEvent_eventForCtrl8WithCipherMusicKmx_ReturnsNoAction { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForCipherMusicTests]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:NSEventModifierFlagControl timestamp:0 windowNumber:0 context:nil characters:@"8" charactersIgnoringModifiers:@"8" isARepeat:NO keyCode:kVK_ANSI_8]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions.count == 0, @"Expected no actions"); @@ -264,10 +264,10 @@ NSString * names[nCombinations]; [KMEngineTests fillInNamesAndModifiersForAllChiralCombinations]; KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileTestMacEngine]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; for (i = 0; i < nCombinations; i++) { - [engine setContextBuffer:@""]; + [engine clearCoreContext]; NSString *charactersIgnoringModifiers = (modifiers[i] & (LEFT_SHIFT_FLAG | RIGHT_SHIFT_FLAG)) ? @"A" : @"a"; NSString * characters = charactersIgnoringModifiers; if (modifiers[i] & (LEFT_ALT_FLAG | RIGHT_ALT_FLAG)) @@ -333,10 +333,10 @@ NSString * names[nCombinations]; [KMEngineTests fillInNamesAndModifiersForAllChiralCombinations]; KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileTestMacEngine]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; for (i = 0; i < nCombinations; i++) { - [engine setContextBuffer:@""]; + [engine clearCoreContext]; NSString *charactersIgnoringModifiers = (modifiers[i] & (LEFT_SHIFT_FLAG | RIGHT_SHIFT_FLAG)) ? @"S" : @"s"; NSString * characters = charactersIgnoringModifiers; if (modifiers[i] & (LEFT_ALT_FLAG | RIGHT_ALT_FLAG)) { @@ -409,7 +409,7 @@ NSString * names[nCombinations]; - (NSString *)checkPlatform_getOutputForKeystroke: (NSString*) character modifierFlags: (NSEventModifierFlags) flag keyCode:(unsigned short)code { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForPlatformTest]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; NSString *lcChar = [character lowercaseString]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:flag timestamp:0 windowNumber:0 context:nil characters:character charactersIgnoringModifiers:lcChar isARepeat:NO keyCode:code]; NSArray *actions = [engine processEvent:event]; @@ -512,7 +512,7 @@ NSString * names[nCombinations]; // KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@"a"]; // NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:@"=" charactersIgnoringModifiers:@"=" isARepeat:NO keyCode:kVK_ANSI_Equal]; KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForIndexOffsetTests]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@"z"]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@"z"]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:@"a" charactersIgnoringModifiers:@"a" isARepeat:NO keyCode:kVK_ANSI_A]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions.count == 2, @"Expected 2 actions"); @@ -541,7 +541,7 @@ NSString * names[nCombinations]; - (void)testCoreProcessEvent_eventForFWithElNuerKmx_ReturnsCorrectCharacter { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForElNuerTests]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@""]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@""]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:@"f" charactersIgnoringModifiers:@"f" isARepeat:NO keyCode:kVK_ANSI_F]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions.count == 1, @"Expected one action"); @@ -552,13 +552,13 @@ NSString * names[nCombinations]; - (void)testCoreProcessEvent_eventDeleteWithElNuerKmx_EmptiesContextReturnsDelete { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForElNuerTests]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@"ɣ"]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@"ɣ"]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:@"\b" charactersIgnoringModifiers:@"\b" isARepeat:NO keyCode:kVK_Delete]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions.count == 1, @"Expected one action"); CoreAction *action = actions[0]; XCTAssert([action isCharacterBackspace], @"Expected CharacterBackspace action"); - NSString *context = engine.contextBuffer; + NSString *context = engine.getCoreContext; XCTAssert([context isEqualToString:@""], @"Context should be empty."); } @@ -577,13 +577,13 @@ NSString * names[nCombinations]; - (void)testCoreProcessEvent_eventReturnWithElNuerKmx_ContextUnchangedReturnsReturn { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForElNuerTests]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@"ɣ"]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@"ɣ"]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:@"\n" charactersIgnoringModifiers:@"\n" isARepeat:NO keyCode:kVK_Return]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions.count == 2, @"Expected one action"); CoreAction *action = actions[1]; XCTAssert(action.actionType == EmitKeystrokeAction, @"Expected EmitKeystrokeAction"); - NSString *context = engine.contextBuffer; + NSString *context = engine.getCoreContext; XCTAssert([context isEqualToString:@"ɣ"], @"Context should be unchanged."); } @@ -601,13 +601,13 @@ NSString * names[nCombinations]; - (void)testCoreProcessEvent_eventTabWithElNuerKmx_ContextUnchangedReturnsTab { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForElNuerTests]; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:@"ɣ"]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:@"ɣ"]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:@"\t" charactersIgnoringModifiers:@"\t" isARepeat:NO keyCode:kVK_Tab]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions.count == 2, @"Expected two actions"); CoreAction *action = actions[1]; XCTAssert(action.actionType == EmitKeystrokeAction, @"Expected EmitKeystrokeAction"); - NSString *context = engine.contextBuffer; + NSString *context = engine.getCoreContext; XCTAssert([context isEqualToString:@"ɣ"], @"Context should be unchanged."); } @@ -626,13 +626,13 @@ NSString * names[nCombinations]; - (void)testCoreProcessEvent_eventSingleQuoteWithElNuerKmx_ReturnsDiacritic { KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileForElNuerTests]; NSString *context = @"ɛ"; - KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile contextBuffer:context]; + KMEngine *engine = [[KMEngine alloc] initWithKMX:kmxFile context:context]; NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:@"'" charactersIgnoringModifiers:@"'" isARepeat:NO keyCode:kVK_ANSI_Quote]; NSArray *actions = [engine processEvent:event]; XCTAssert(actions.count == 1, @"Expected one action"); CoreAction *action = actions[0]; XCTAssert([action isCharacter], @"Expected CharacterAction"); - context = engine.contextBuffer; + context = engine.getCoreContext; XCTAssert([context isEqualToString:@"\u025B\u0308"], @"Context updated with diacritic."); }