Skip to content

Commit

Permalink
Fix cannot find control char when text length exceeds MAX_MENTION_QUE…
Browse files Browse the repository at this point in the history
…RY_LENGTH (#243)

* Fix cannot find control char when text length exceeds MAX_MENTION_QUERY_LENGTH

* Update HKWMentionsPluginV2.m

* add config static property

* Update HKWTextView+TextTransformation.m

* Update HKWTextView+TextTransformation.m
  • Loading branch information
krayc425 authored Mar 16, 2023
1 parent 4e3f1e3 commit 25bcd0f
Show file tree
Hide file tree
Showing 12 changed files with 69 additions and 27 deletions.
Binary file removed .DS_Store
Binary file not shown.
4 changes: 2 additions & 2 deletions Hakawai/Core/HKWControlFlowPluginProtocols.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
/*!
If available, this method is called when the text view is programatically updated (e.g. setText: or setAttributedText:)
*/
-(void) textViewDidProgrammaticallyUpdate:(UITextView *)textView;
- (void)textViewDidProgrammaticallyUpdate:(UITextView *)textView;

/*!
If available, this method is called when the text view is about to engage in a programmatic custom pasting of text
Expand Down Expand Up @@ -75,7 +75,7 @@
/*!
If available, this method is called when the text view is programatically updated (e.g. setText: or setAttributedText:)
*/
-(void) textViewDidProgrammaticallyUpdate:(UITextView *)textView;
- (void)textViewDidProgrammaticallyUpdate:(UITextView *)textView;

// UITextViewDelegate optional helper methods
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView;
Expand Down
2 changes: 1 addition & 1 deletion Hakawai/Core/HKWTextView+TextTransformation.m
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ - (void)insertAttributedText:(NSAttributedString *)text location:(NSUInteger)loc
QuickPath keyboard will hold a NSConditionLock on attributedText while accessing to it at the same time results in a deadlock. Calling main queue
to make sure it won't be synchronized to cause a deadlock. Apple Feedback Tracking number: [FB6828895]
*/
-(void)insertTextAttachment:(NSTextAttachment *)attachment location:(NSUInteger)location {
- (void)insertTextAttachment:(NSTextAttachment *)attachment location:(NSUInteger)location {
[self insertTextAttachmentImpl:attachment location:location];
}

Expand Down
20 changes: 11 additions & 9 deletions Hakawai/Core/HKWTextView.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface HKWTextView : UITextView

+ (BOOL) enableMentionsPluginV2;
+ (BOOL) directlyUpdateQueryWithCustomDelegate;
+ (BOOL) enableControlCharactersToPrepend;
+ (void) setEnableMentionsPluginV2:(BOOL)enabled;
+ (void) setDirectlyUpdateQueryWithCustomDelegate:(BOOL)enabled;
+ (void) setEnableControlCharactersToPrepend:(BOOL)enabled;
+ (BOOL)enableMentionsPluginV2;
+ (BOOL)directlyUpdateQueryWithCustomDelegate;
+ (BOOL)enableControlCharactersToPrepend;
+ (BOOL)enableControlCharacterMaxLengthFix;
+ (void)setEnableMentionsPluginV2:(BOOL)enabled;
+ (void)setDirectlyUpdateQueryWithCustomDelegate:(BOOL)enabled;
+ (void)setEnableControlCharactersToPrepend:(BOOL)enabled;
+ (void)setEnableControlCharacterMaxLengthFix:(BOOL)enabled;

#pragma mark - Initialization

Expand Down Expand Up @@ -79,7 +81,7 @@ NS_ASSUME_NONNULL_BEGIN
\note If a abstraction layer control flow plug-in is registered, setting this method will unregister the other plug-in;
however, setting this to nil if an abstraction layer plug-in is registered will do nothing.
*/
@property (nonatomic, strong, nullable) id<HKWDirectControlFlowPluginProtocol>controlFlowPlugin;
@property (nonatomic, strong, nullable) id<HKWDirectControlFlowPluginProtocol> controlFlowPlugin;

/*!
Register a control flow plug-in with the editor. Unlike simple plug-ins, only one control flow plug-in can be enabled
Expand All @@ -88,7 +90,7 @@ NS_ASSUME_NONNULL_BEGIN
\note If a abstraction layer control flow plug-in is registered, setting this method will unregister the other plug-in;
however, setting this to nil if a direct plug-in is registered will do nothing.
*/
@property (nonatomic, strong, nullable) id<HKWAbstractionLayerControlFlowPluginProtocol>abstractionControlFlowPlugin;
@property (nonatomic, strong, nullable) id<HKWAbstractionLayerControlFlowPluginProtocol> abstractionControlFlowPlugin;

/*!
Register a simple plug-in with the editor.
Expand All @@ -104,7 +106,7 @@ NS_ASSUME_NONNULL_BEGIN
Inform the the textview that it was programatically updated (e.g. setText: or setAttributedText:) so that associated
plugins can update their state accordingly.
*/
-(void) textViewDidProgrammaticallyUpdate;
- (void)textViewDidProgrammaticallyUpdate;

#pragma mark - API (plug-in status)

Expand Down
11 changes: 10 additions & 1 deletion Hakawai/Core/HKWTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ @interface HKWTextView () <UITextViewDelegate, HKWAbstractionLayerDelegate>
static BOOL enableMentionsPluginV2 = NO;
static BOOL directlyUpdateQueryWithCustomDelegate = NO;
static BOOL enableControlCharactersToPrepend = NO;
static BOOL enableControlCharacterMaxLengthFix = YES;

@implementation HKWTextView

Expand All @@ -66,6 +67,14 @@ + (BOOL)enableControlCharactersToPrepend {
return enableControlCharactersToPrepend;
}

+ (BOOL)enableControlCharacterMaxLengthFix {
return enableControlCharacterMaxLengthFix;
}

+ (void)setEnableControlCharacterMaxLengthFix:(BOOL)enabled {
enableControlCharacterMaxLengthFix = enabled;
}

+ (void)setEnableControlCharactersToPrepend:(BOOL)enabled {
enableControlCharactersToPrepend = enabled;
}
Expand Down Expand Up @@ -356,7 +365,7 @@ - (void)touchOverlayViewTapped:(UITapGestureRecognizer *)gestureRecognizer {
}
}

-(void) textViewDidProgrammaticallyUpdate {
- (void)textViewDidProgrammaticallyUpdate {

if ([self.controlFlowPlugin respondsToSelector:@selector(textViewDidProgrammaticallyUpdate:)]) {
[self.controlFlowPlugin textViewDidProgrammaticallyUpdate:self];
Expand Down
2 changes: 1 addition & 1 deletion Hakawai/Mentions/HKWMentionsPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ typedef NS_ENUM(NSInteger, HKWMentionsPluginState) {
/*!
Inform the plugin that the textview was programatically updated (e.g. setText: or setAttributedText:)
*/
-(void)textViewDidProgrammaticallyUpdate:(UITextView *_Null_unspecified)textView;
- (void)textViewDidProgrammaticallyUpdate:(UITextView *_Null_unspecified)textView;

/*!
If available, this method is called when the text view is about to engage in a programmatic custom pasting of text
Expand Down
2 changes: 1 addition & 1 deletion Hakawai/Mentions/HKWMentionsPluginV1.m
Original file line number Diff line number Diff line change
Expand Up @@ -1432,7 +1432,7 @@ - (void)dataReturnedWithEmptyResults:(BOOL)isEmptyResults
[self.creationStateMachine dataReturnedWithEmptyResults:isEmptyResults keystringEndsWithWhiteSpace:keystringEndsWithWhiteSpace];
}

-(void) textViewDidProgrammaticallyUpdate:(UITextView *)textView {
- (void)textViewDidProgrammaticallyUpdate:(UITextView *)textView {
if (self.state == HKWMentionsStartDetectionStateCreatingMention) {
[self.creationStateMachine cancelMentionCreation];
} else {
Expand Down
16 changes: 10 additions & 6 deletions Hakawai/Mentions/HKWMentionsPluginV2.m
Original file line number Diff line number Diff line change
Expand Up @@ -749,10 +749,11 @@ - (NSUInteger)endOfValidWordInText:(nonnull NSString *)text afterLocation:(NSUIn
/**
Search backwards in a string for a character in the control character set

@param text The text in which to perform a backwards search for a control character
@returns Location for most recent control character in a string
@param text The text in which to perform a backwards search for a control character. Note that this text can be a trimmed one if the orignal text's length exceeds @c MAX_MENTION_QUERY_LENGTH .
@param locationOffsetInOriginalText The offset of @c originalText.length - @c MAX_MENTION_QUERY_LENGTH to locate the correct char.
@returns Location for most recent control character in a @c text
*/
- (NSUInteger)mostRecentControlCharacterLocationInText:(NSString *)text {
- (NSUInteger)mostRecentControlCharacterLocationInText:(NSString *)text locationOffsetInOriginalText:(NSUInteger)locationOffsetInOriginalText {
if (text.length == 0) {
return NSNotFound;
}
Expand All @@ -762,9 +763,11 @@ - (NSUInteger)mostRecentControlCharacterLocationInText:(NSString *)text {
unichar character = [text characterAtIndex:unsignedIndex];
if ([self.controlCharacterSet characterIsMember:character]) {
// If the most recent control character has mention attribute, return NSNotFound
// Note that here we need to add back the locationOffsetInOriginalText which represents `originalText.length - trimmedText.length`.
NSUInteger location = HKWTextView.enableControlCharacterMaxLengthFix ? (unsignedIndex + locationOffsetInOriginalText) : unsignedIndex;
if (HKWTextView.enableControlCharactersToPrepend
&& [self.controlCharactersToPrepend characterIsMember:character]
&& [self mentionAttributeAtLocation:unsignedIndex range:nil]) {
&& [self mentionAttributeAtLocation:location range:nil]) {
return NSNotFound;
}
return unsignedIndex;
Expand All @@ -786,7 +789,8 @@ - (NSUInteger)mostRecentValidControlCharacterLocation:(NSString *)text beforeLoc
NSUInteger maximumSearchIndex = (NSUInteger)MAX((int)location-MAX_MENTION_QUERY_LENGTH, 0);
NSString *substringToSearchForControlChar = [substringUntilLocation substringFromIndex:maximumSearchIndex];
// Find control character location
NSUInteger controlCharLocation = [self mostRecentControlCharacterLocationInText:substringToSearchForControlChar];
NSUInteger controlCharLocation = [self mostRecentControlCharacterLocationInText:substringToSearchForControlChar
locationOffsetInOriginalText:maximumSearchIndex];
if (controlCharLocation != NSNotFound) {
// If it exists, offset it by the search index to get actual location in the parent text view
controlCharLocation = controlCharLocation + maximumSearchIndex;
Expand Down Expand Up @@ -1237,7 +1241,7 @@ - (void)createMention:(HKWMentionsAttribute *)mention cursorLocation:(NSUInteger
NSRange rangeToTransform;
// Find where previous control character was, and replace mention at that point
NSString *substringUntilCursor = [parentTextView.text substringToIndex:cursorLocation];
NSUInteger controlCharLocation = [self mostRecentControlCharacterLocationInText:substringUntilCursor];
NSUInteger controlCharLocation = [self mostRecentControlCharacterLocationInText:substringUntilCursor locationOffsetInOriginalText:0];
// Prepend control character to mentionText if needed
if (HKWTextView.enableControlCharactersToPrepend && controlCharLocation != NSNotFound) {
unichar controlCharacter = [parentTextView.text characterAtIndex:controlCharLocation];
Expand Down
4 changes: 1 addition & 3 deletions Hakawai/Mentions/HKWMentionsStartDetectionStateMachine.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ + (instancetype)stateMachineWithDelegate:(id<HKWMentionsStartDetectionStateMachi
return sm;
}

-(void) resetStateUsingString:(NSString *)string {

- (void)resetStateUsingString:(NSString *)string {
self.state = HKWMentionsStartDetectionStateQuiescentReady;
self.charactersSinceLastWhitespace = 0;

Expand All @@ -83,7 +82,6 @@ -(void) resetStateUsingString:(NSString *)string {
self.charactersSinceLastWhitespace = self.stringBuffer.length - NSMaxRange(lastWhitespaceRange);
}
}

}

- (void)validStringInserted:(NSString *)string
Expand Down
2 changes: 1 addition & 1 deletion Hakawai/Mentions/_HKWMentionsStartDetectionStateMachine.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ withCharacterNowPrecedingCursor:(unichar)precedingChar
/*!
Inform the state machine that the attached control view has reset it's state, and now represents the specified string
*/
-(void) resetStateUsingString:(NSString *)string;
- (void)resetStateUsingString:(NSString *)string;

/*!
Return characters after given location till whitespace is encountered.
Expand Down
1 change: 1 addition & 0 deletions HakawaiDemo/HakawaiDemo/MentionsDemoViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ - (void)viewDidLoad {
if (HKWTextView.enableMentionsPluginV2) {
mentionsPlugin = [HKWMentionsPluginV2 mentionsPluginWithChooserMode:mode
controlCharacters:controlCharacters
controlCharactersToPrepend:controlCharacters
searchLength:3];
} else {
mentionsPlugin = [HKWMentionsPluginV1 mentionsPluginWithChooserMode:mode
Expand Down
32 changes: 30 additions & 2 deletions HakawaiTests/HKWMentionsPluginTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ @interface HKWMentionsPluginV2 ()
@property (nonatomic, strong, nullable) NSCharacterSet *controlCharactersToPrepend;
- (BOOL)stringValidForMentionsCreation:(NSString *)string;
- (void)createMention:(HKWMentionsAttribute *)mention cursorLocation:(NSUInteger)cursorLocation;
- (NSUInteger)mostRecentControlCharacterLocationInText:(NSString *)text;
- (NSUInteger)mostRecentControlCharacterLocationInText:(NSString *)text locationOffsetInOriginalText:(NSUInteger)locationOffsetInOriginalText;
- (NSUInteger)mostRecentValidControlCharacterLocation:(NSString *)text beforeLocation:(NSUInteger)location;
@end

// Methods for testing attribute values/ranges in pluginV2
Expand All @@ -44,6 +45,9 @@ - (NSRange)rangeForAttributeWithName:(NSAttributedStringKey)attrName forWordOfLe

@implementation HKWMentionsPluginV2 (Testing)

// Has to be consistent with `HKWMentionsPluginV2`.
static NSUInteger MAX_MENTION_QUERY_LENGTH = 100;

- (id)valueForAttributeWithName:(NSAttributedStringKey)attrName forWordOfLength:(NSUInteger)length {
__block id returnValue;
__strong __auto_type parentTextView = self.parentTextView;
Expand Down Expand Up @@ -1061,7 +1065,7 @@ - (CGFloat)heightForCellForMentionsEntity:(id<HKWMentionsEntityProtocol> _Null_u
textView.text = [textView.text stringByAppendingString:@" test"];
[mentionsPlugin addMention:attribute];

expect([mentionsPlugin mostRecentControlCharacterLocationInText:@"@FirstName LastName test"]).to.equal(NSNotFound);
expect([mentionsPlugin mostRecentControlCharacterLocationInText:@"@FirstName LastName test" locationOffsetInOriginalText:0]).to.equal(NSNotFound);
});

it(@"highlight mention text with control character prepended", ^{
Expand All @@ -1078,6 +1082,30 @@ - (CGFloat)heightForCellForMentionsEntity:(id<HKWMentionsEntityProtocol> _Null_u
expect(highlightedRange.length).to.equal(attribute.mentionText.length);
expect([highlightedAttribute class]).to.equal([HKWRoundedRectBackgroundAttributeValue class]);
});

it(@"can find control character when text length exceeds MAX_MENTION_QUERY_LENGTH", ^{
HKWTextView.enableControlCharacterMaxLengthFix = YES;
mentionsPlugin.controlCharactersToPrepend = HKWExternalMentionConstants.atSymbols;
NSString *text = @"";
// Append a string with 10 chars for 10 times
NSString *singleText = @"@abcdefghi";
for (NSUInteger i = 0; i <= 10; i++) {
text = [text stringByAppendingString:singleText];
}
// Append a control char at the end (text = 100 char + "@")
text = [text stringByAppendingString:@"@"];
textView.text = text;
// Add attributes to first 100 char
for (NSUInteger i = 0; i <= 10; i++) {
HKWMentionsAttribute *attribute = [HKWMentionsAttribute mentionWithText:singleText identifier:[NSString stringWithFormat:@"%lud", (unsigned long)i]];
attribute.range = NSMakeRange(i * singleText.length, singleText.length);
[mentionsPlugin addMention:attribute];
}
// Make sure text is the same
expect(textView.text).to.equal(text);
// Make sure we can find the latest control char at the very end
expect([mentionsPlugin mostRecentValidControlCharacterLocation:textView.text beforeLocation:(NSUInteger)(textView.text.length)]).to.equal(textView.text.length - 1);
});
});

describe(@"highlight mentions - MENTIONS PLUGIN V2", ^{
Expand Down

0 comments on commit 25bcd0f

Please sign in to comment.