Fix deltas when selection is active and composing begins on MacOS (#32412)
* Fix special character input on a selection
* Add test for composing when selection is active
Co-authored-by: Renzo Olivares <roliv@google.com>
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm
index 4434952..621f243 100644
--- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm
+++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm
@@ -629,6 +629,7 @@
_activeModel->BeginComposing();
}
flutter::TextRange composingBeforeChange = _activeModel->composing_range();
+ flutter::TextRange selectionBeforeChange = _activeModel->selection();
// Input string may be NSString or NSAttributedString.
BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
@@ -646,7 +647,10 @@
if (_enableDeltaModel) {
[self updateEditStateWithDelta:flutter::TextEditingDelta(textBeforeChange,
- composingBeforeChange, marked_text)];
+ selectionBeforeChange.collapsed()
+ ? composingBeforeChange
+ : selectionBeforeChange,
+ marked_text)];
} else {
[self updateEditState];
}
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm
index 17c50b2..a724b11 100644
--- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm
+++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm
@@ -845,6 +845,78 @@
return true;
}
+- (bool)testComposingWithDeltasWhenSelectionIsActive {
+ id engineMock = OCMClassMock([FlutterEngine class]);
+ id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
+ OCMStub( // NOLINT(google-objc-avoid-throwing-exception)
+ [engineMock binaryMessenger])
+ .andReturn(binaryMessengerMock);
+
+ FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock
+ nibName:@""
+ bundle:nil];
+
+ FlutterTextInputPlugin* plugin =
+ [[FlutterTextInputPlugin alloc] initWithViewController:viewController];
+
+ [plugin handleMethodCall:[FlutterMethodCall
+ methodCallWithMethodName:@"TextInput.setClient"
+ arguments:@[
+ @(1), @{
+ @"inputAction" : @"action",
+ @"enableDeltaModel" : @"true",
+ @"inputType" : @{@"name" : @"inputName"},
+ }
+ ]]
+ result:^(id){
+ }];
+
+ FlutterMethodCall* call = [FlutterMethodCall methodCallWithMethodName:@"TextInput.setEditingState"
+ arguments:@{
+ @"text" : @"Text",
+ @"selectionBase" : @(0),
+ @"selectionExtent" : @(4),
+ @"composingBase" : @(-1),
+ @"composingExtent" : @(-1),
+ }];
+ [plugin handleMethodCall:call
+ result:^(id){
+ }];
+
+ [plugin setMarkedText:@"~"
+ selectedRange:NSMakeRange(1, 0)
+ replacementRange:NSMakeRange(NSNotFound, 0)];
+
+ NSDictionary* deltaToFramework = @{
+ @"oldText" : @"Text",
+ @"deltaText" : @"~",
+ @"deltaStart" : @(0),
+ @"deltaEnd" : @(4),
+ @"selectionBase" : @(1),
+ @"selectionExtent" : @(1),
+ @"selectionAffinity" : @"TextAffinity.upstream",
+ @"selectionIsDirectional" : @(false),
+ @"composingBase" : @(0),
+ @"composingExtent" : @(1),
+ };
+ NSDictionary* expectedState = @{
+ @"deltas" : @[ deltaToFramework ],
+ };
+
+ NSData* updateCall = [[FlutterJSONMethodCodec sharedInstance]
+ encodeMethodCall:[FlutterMethodCall
+ methodCallWithMethodName:@"TextInputClient.updateEditingStateWithDeltas"
+ arguments:@[ @(1), expectedState ]]];
+
+ @try {
+ OCMVerify( // NOLINT(google-objc-avoid-throwing-exception)
+ [binaryMessengerMock sendOnChannel:@"flutter/textinput" message:updateCall]);
+ } @catch (...) {
+ return false;
+ }
+ return true;
+}
+
- (bool)testLocalTextAndSelectionUpdateAfterDelta {
id engineMock = OCMClassMock([FlutterEngine class]);
id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
@@ -957,6 +1029,10 @@
ASSERT_TRUE([[FlutterInputPluginTestObjc alloc] testComposingWithDelta]);
}
+TEST(FlutterTextInputPluginTest, testComposingWithDeltasWhenSelectionIsActive) {
+ ASSERT_TRUE([[FlutterInputPluginTestObjc alloc] testComposingWithDeltasWhenSelectionIsActive]);
+}
+
TEST(FlutterTextInputPluginTest, TestLocalTextAndSelectionUpdateAfterDelta) {
ASSERT_TRUE([[FlutterInputPluginTestObjc alloc] testLocalTextAndSelectionUpdateAfterDelta]);
}