Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,20 @@ - (UITextRange*)rangeEnclosingPosition:(UITextPosition*)position
}

- (UITextRange*)lineEnclosingPosition:(UITextPosition*)position {
// TODO(hellohuanlin): Remove iOS 17 check. The same logic should apply to older versions too.
if (@available(iOS 17.0, *)) {
FlutterTextPosition* flutterPosition = (FlutterTextPosition*)position;
if (flutterPosition.index > _textInputView.text.length) {
return nil;
}
// end of document with forward affinity is still considered as part of the document, which is
// used by voice control's delete line command.
if (flutterPosition.index == _textInputView.text.length &&
flutterPosition.affinity == UITextStorageDirectionBackward) {
return nil;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would happen if this returns the last line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we return the last line, we will see the 2 bugs linked in my description.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, if endOfDocument is exclusive and does return (text.length, upstream), then it is contradicting with how flutter handle text position.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(text.length, downstream) is a valid cursor position as well. In most cases (text.length, upstream) and (text.length, downstream) refer to the same cursor positoin, but when there's bidi text at the end of the text they refer to different locations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried in UITextField. It's returning 7F for a string that's 7 code units long. So it's inclusive I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can play around with it more tomorrow, but afaik apple's sample project uses exclusive endOfDocument. (It does not use affinity tho)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah cursor position and text position have different end positions. I'm not sure which one UITextField is using.

}
}

// Gets the first line break position after the input position.
NSString* textAfter = [_textInputView
textInRange:[_textInputView textRangeFromPosition:position
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2660,6 +2660,47 @@ - (void)testFlutterTokenizerCanParseLines {
XCTAssertEqual(range.range.length, 20u);
}

- (void)testFlutterTokenizerLineRangeQueryWithEndOfDocumentAndBackwardAffinity {
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
id<UITextInputTokenizer> tokenizer = [inputView tokenizer];

NSString* text = @"Random text\nwith 2 lines";
[inputView insertText:text];

FlutterTextPosition* endOfDocumentWithBackwardAffinity =
[FlutterTextPosition positionWithIndex:text.length affinity:UITextStorageDirectionBackward];

FlutterTextRange* range =
(FlutterTextRange*)[tokenizer rangeEnclosingPosition:endOfDocumentWithBackwardAffinity
withGranularity:UITextGranularityLine
inDirection:UITextLayoutDirectionRight];

if (@available(iOS 17.0, *)) {
XCTAssertNil(range);
} else {
XCTAssertEqual(range.range.location, 12u);
XCTAssertEqual(range.range.length, 12u);
}
}

- (void)testFlutterTokenizerLineRangeQueryWithEndOfDocumentAndForwardAffinity {
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
id<UITextInputTokenizer> tokenizer = [inputView tokenizer];

NSString* text = @"Random text\nwith 2 lines";
[inputView insertText:text];

FlutterTextPosition* endOfDocumentWithForwardAffinity =
[FlutterTextPosition positionWithIndex:text.length affinity:UITextStorageDirectionForward];

range = (FlutterTextRange*)[tokenizer rangeEnclosingPosition:endOfDocumentWithForwardAffinity
withGranularity:UITextGranularityLine
inDirection:UITextLayoutDirectionRight];

XCTAssertEqual(range.range.location, 12u);
XCTAssertEqual(range.range.length, 12u);
}

- (void)testFlutterTextInputPluginRetainsFlutterTextInputView {
FlutterViewController* flutterViewController = [[FlutterViewController alloc] init];
FlutterTextInputPlugin* myInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:engine];
Expand Down