eliminate some unneeded properties

This commit is contained in:
sgschantz 2023-10-04 11:14:17 +07:00
parent af25f2b466
commit cd037c0b8d

View file

@ -6,19 +6,28 @@
*
* Created by Shawn Schantz on 2023-05-05.
*
* Check whether the current text input client is compatibility with APIs.
* Compliance is determined by attempting the current selection (location and length),
* but this is not a surefire indicator. For some apps, a valid but incorrect selection of
* {0, 0} is returned regardless of the real selection.
* Check whether the current text input client is compliant with APIs.
* Compliance is determined by getting the current selection (location and length) with
* the selectionRange API and evaluating the results.
* But this is not a surefire indicator. For several apps, a valid but incorrect selection of
* {0, 0}, or possibly something else, is returned regardless of the real selection.
*
* When {0, 0} is returned, then we can check to see if a subsequent insert results in a
* change in the location. It always should, because it cannot replace at location 0.
* If the location does change, then the selection API is compliant.
* When {0, 0} is returned, then we can check to see if a later call to the insertText API
* results in a change in the location. It always should, because there is no text to
* replace if the client truly is at location 0.
* If the location does change, then we assume the selection API is compliant.
*
* Even worse for some apps, the selection API works, but attempts
* to replace during an insert do not work. There is no way to detect this behavior,
* so these apps must be hard-coded as non-compliant. A couple of known apps
* that behave this way are Brackets (Adobe OS project) and MacVIM
* Even worse for some apps, the selection API returns a non-zero location, but attempts
* to replace text during a call to insertText do not function properly. There is no way to
* detect this behavior, so these apps must be hard-coded as non-compliant. A couple
* of known apps that behave this way are Brackets (Adobe OS project) and MacVIM
*
* We really only have the ability to test the selection API, and if it does not work, then
* Keyman functionality is limited. Lacking the current location
* - we cannot pass a range for reading the context
* - we cannot pass a replacement range when calling insertText
* The result is that we are limited in key processing, using only locally cached context, and
* we can only delete by sending backspace events.
*/
#import "TextCompatibilityCheck.h"
@ -31,8 +40,6 @@ NSString *const kKMLegacyApps = @"KMLegacyApps";
@property (readonly) id client;
@property BOOL apiComplianceUncertain;
@property BOOL hasCompliantSelectionApi;
@property (readonly) BOOL hasReadApi;
@property (readonly) BOOL hasInsertApi;
@end
@ -45,27 +52,17 @@ NSString *const kKMLegacyApps = @"KMLegacyApps";
_clientApplicationId = appId;
_apiComplianceUncertain = YES;
// first check in the noncompliant app lists
// TODO: uncomment after testing
//BOOL isUncompliantApp = ![self containedInNoncompliantAppLists:clientAppId];
BOOL isUncompliantApp = NO;
if (isUncompliantApp) {
_apiComplianceUncertain = NO;
self.hasCompliantSelectionApi = NO;
} else {
// if we do not have hard-coded noncompliance, then test the app
if (![self applyNoncompliantAppLists:appId]) {
[self testApiCompliance:client];
}
_hasReadApi = [client respondsToSelector:@selector(attributedSubstringFromRange:)];
_hasInsertApi = [client respondsToSelector:@selector(insertText:replacementRange:)];
}
return self;
}
-(NSString *)description
{
return [NSString stringWithFormat:@"apiComplianceUncertain: %d, hasWorkingSelectionApi: %d, hasReadAPI: %d, hasInsertAPI: %d, canGetSelection: %d, canReadText: %d, canInsertText: %d, canReplaceText: %d, mustBackspaceUsingEvents: %d, clientAppId: %@, client: %@", self.apiComplianceUncertain, self.hasCompliantSelectionApi, self.hasReadApi, self.hasInsertApi, [self canGetSelection], [self canReadText], [self canInsertText], [self canReplaceText], [self mustBackspaceUsingEvents], _clientApplicationId, _client];
return [NSString stringWithFormat:@"apiComplianceUncertain: %d, hasCompliantSelectionApi: %d, canReadText: %d, canReplaceText: %d, mustBackspaceUsingEvents: %d, clientAppId: %@, client: %@", self.apiComplianceUncertain, self.hasCompliantSelectionApi, [self canReadText], [self canReplaceText], [self mustBackspaceUsingEvents], _clientApplicationId, _client];
}
/** test to see if the API selectedRange functions properly for the text input client */
@ -136,16 +133,22 @@ return [NSString stringWithFormat:@"apiComplianceUncertain: %d, hasWorkingSelect
}
/**
* 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
* Apply the hard-coded non-compliant app list and the user-managed (via user defaults) non-compliant app list.
* If true, mark the app as non-compliant and certain (not apiComplianceUncertain).
*/
- (BOOL)containedInNoncompliantAppLists:(NSString *)clientAppId {
- (BOOL)applyNoncompliantAppLists:(NSString *)clientAppId {
BOOL isAppNonCompliant = [self containedInHardCodedNoncompliantAppList:clientAppId];
if (!isAppNonCompliant) {
isAppNonCompliant = [self containedInUserManagedNoncompliantAppList:clientAppId];
}
NSLog(@"containedInNoncompliantAppLists: for app %@: %@", clientAppId, isAppNonCompliant?@"yes":@"no");
if (isAppNonCompliant) {
self.apiComplianceUncertain = NO;
self.hasCompliantSelectionApi = NO;
}
return isAppNonCompliant;
}
@ -157,6 +160,8 @@ return [NSString stringWithFormat:@"apiComplianceUncertain: %d, hasWorkingSelect
BOOL isAppNonCompliant = ([clientAppId isEqual: @"com.github.atom"] ||
[clientAppId isEqual: @"com.collabora.libreoffice-free"] ||
[clientAppId isEqual: @"org.libreoffice.script"] ||
[clientAppId isEqual: @"org.vim.MacVim"] ||
[clientAppId isEqual: @"io.brackets.appshell"] ||
[clientAppId isEqual: @"com.axosoft.gitkraken"] ||
[clientAppId isEqual: @"org.sil.app.builder.scripture.ScriptureAppBuilder"] ||
[clientAppId isEqual: @"org.sil.app.builder.reading.ReadingAppBuilder"] ||
@ -201,53 +206,6 @@ return [NSString stringWithFormat:@"apiComplianceUncertain: %d, hasWorkingSelect
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 = NO;
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
*/
@ -275,25 +233,16 @@ return [NSString stringWithFormat:@"apiComplianceUncertain: %d, hasWorkingSelect
return self.apiComplianceUncertain;
}
-(BOOL) canGetSelection {
return self.hasCompliantSelectionApi;
}
-(BOOL) canReadText {
// seems simple, but every application tested that returned the selection also successfully read the text using attributedSubstringFromRange
BOOL canReadText = self.hasReadApi && self.canGetSelection;
return canReadText;
}
-(BOOL) canInsertText {
// all applications tested can insertText (though replacement may not work)
return self.hasInsertApi;
BOOL hasReadApi = [self.client respondsToSelector:@selector(attributedSubstringFromRange:)];
return hasReadApi && self.hasCompliantSelectionApi;
}
-(BOOL) canReplaceText {
// testing shows that every application that returns the selection, can also replace text
return self.hasInsertApi && self.canGetSelection;
return self.hasCompliantSelectionApi;
}
-(BOOL) mustBackspaceUsingEvents {
@ -301,43 +250,4 @@ return [NSString stringWithFormat:@"apiComplianceUncertain: %d, hasWorkingSelect
return !self.canReplaceText;
}
/*
-(void) experiment {
NSRange notFoundRange = NSMakeRange(NSNotFound, NSNotFound);
NSRange zeroRange = NSMakeRange(0, 0);
NSRange selectionRange = [self.client selectedRange];
if (NSEqualRanges(selectionRange, notFoundRange)) {
NSLog(@"**compatibility** ERROR selectedRange = {NSNotFound, NSNotFound}");
} else if (NSEqualRanges(selectionRange, notFoundRange)) {
NSLog(@"**compatibility** ERROR selectedRange = {0,0}");
} else {
NSLog(@"**compatibility** selectedRange = %@", NSStringFromRange(selectionRange));
}
NSRange contextRange = NSMakeRange(1, 0);
NSLog(@"**compatibility** get string of empty range, location 1, length 0");
NSAttributedString *attributedText = [self.client attributedSubstringFromRange:contextRange];
if (attributedText == nil) {
NSLog(@" **compatibility** attributedSubstringFromRange({1,0}) is nil");
} else {
NSString *context = attributedText.string;
NSLog(@" **compatibility** attributedSubstringFromRange = %@", context);
}
contextRange = NSMakeRange(1, 1);
NSLog(@"**compatibility** get string at location 1, length 1");
attributedText = [self.client attributedSubstringFromRange:contextRange];
if (attributedText == nil) {
NSLog(@" **compatibility** attributedSubstringFromRange({1,1}) is nil");
} else {
NSString *context = attributedText.string;
NSLog(@" **compatibility** attributedSubstringFromRange = %@", context);
}
}
*/
@end