diff --git a/shell/platform/common/text_input_model.cc b/shell/platform/common/text_input_model.cc index bfdc7c42e2547..a21fe1ff3effa 100644 --- a/shell/platform/common/text_input_model.cc +++ b/shell/platform/common/text_input_model.cc @@ -69,15 +69,22 @@ void TextInputModel::BeginComposing() { composing_range_ = TextRange(selection_.start()); } -void TextInputModel::UpdateComposingText(const std::u16string& text) { +void TextInputModel::UpdateComposingText(const std::u16string& text, + const TextRange& selection) { // Preserve selection if we get a no-op update to the composing region. if (text.length() == 0 && composing_range_.collapsed()) { return; } - DeleteSelected(); - text_.replace(composing_range_.start(), composing_range_.length(), text); + const TextRange& rangeToDelete = + composing_range_.collapsed() ? selection_ : composing_range_; + text_.replace(rangeToDelete.start(), rangeToDelete.length(), text); composing_range_.set_end(composing_range_.start() + text.length()); - selection_ = TextRange(composing_range_.end()); + selection_ = TextRange(selection.start() + composing_range_.start(), + selection.extent() + composing_range_.start()); +} + +void TextInputModel::UpdateComposingText(const std::u16string& text) { + UpdateComposingText(text, TextRange(text.length())); } void TextInputModel::UpdateComposingText(const std::string& text) { diff --git a/shell/platform/common/text_input_model.h b/shell/platform/common/text_input_model.h index cd03e1fc864fa..2546e56bbe84e 100644 --- a/shell/platform/common/text_input_model.h +++ b/shell/platform/common/text_input_model.h @@ -57,12 +57,17 @@ class TextInputModel { // are restricted to the composing range. void BeginComposing(); - // Replaces the composing range with new UTF-16 text. + // Replaces the composing range with new UTF-16 text, and sets the selection. // - // If a selection of non-zero length exists, it is deleted if the composing - // text is non-empty. The composing range is adjusted to the length of - // |text| and the selection base and offset are set to the end of the - // composing range. + // The given |text| replaces text within the current composing range, or the + // current selection if the text wasn't composing. The composing range is + // adjusted to the length of |text|, and the |selection| describes the new + // selection range, relative to the start of the new composing range. + void UpdateComposingText(const std::u16string& text, + const TextRange& selection); + + // Replaces the composing range with new UTF-16 text and sets the selection to + // the end of the composing text. void UpdateComposingText(const std::u16string& text); // Replaces the composing range with new UTF-8 text. diff --git a/shell/platform/common/text_input_model_unittests.cc b/shell/platform/common/text_input_model_unittests.cc index c138fd1fac3b4..9e8ed8f218a4e 100644 --- a/shell/platform/common/text_input_model_unittests.cc +++ b/shell/platform/common/text_input_model_unittests.cc @@ -403,6 +403,17 @@ TEST(TextInputModel, UpdateComposingRemovesLastComposingCharacter) { model->SetText("ACDE"); } +TEST(TextInputModel, UpdateSelectionWhileComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + model->SetComposingRange(TextRange(4, 5), 1); + model->UpdateComposingText(u"ぴょんぴょん", TextRange(3, 6)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDぴょんぴょん"); + EXPECT_EQ(model->selection(), TextRange(7, 10)); + EXPECT_EQ(model->composing_range(), TextRange(4, 10)); +} + TEST(TextInputModel, AddCodePoint) { auto model = std::make_unique(); model->AddCodePoint('A'); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index 80e7fb946a58a..33adfa909dc65 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -67,9 +67,9 @@ #pragma mark - Enums /** - * The affinity of the current cursor position. If the cursor is at a position representing - * a line break, the cursor may be drawn either at the end of the current line (upstream) - * or at the beginning of the next (downstream). + * The affinity of the current cursor position. If the cursor is at a position + * representing a soft line break, the cursor may be drawn either at the end of + * the current line (upstream) or at the beginning of the next (downstream). */ typedef NS_ENUM(NSUInteger, FlutterTextAffinity) { kFlutterTextAffinityUpstream, @@ -852,19 +852,13 @@ - (void)setMarkedText:(id)string // Input string may be NSString or NSAttributedString. BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; - std::string marked_text = isAttributedString ? [[string string] UTF8String] : [string UTF8String]; - _activeModel->UpdateComposingText(marked_text); - - // Update the selection within the marked text. - long signedLength = static_cast(selectedRange.length); - long location = selectedRange.location + _activeModel->composing_range().base(); - long textLength = _activeModel->text_range().end(); - - size_t base = std::clamp(location, 0L, textLength); - size_t extent = std::clamp(location + signedLength, 0L, textLength); - _activeModel->SetSelection(flutter::TextRange(base, extent)); + const NSString* rawString = isAttributedString ? [string string] : string; + _activeModel->UpdateComposingText( + (const char16_t*)[rawString cStringUsingEncoding:NSUTF16StringEncoding], + flutter::TextRange(selectedRange.location, selectedRange.location + selectedRange.length)); if (_enableDeltaModel) { + std::string marked_text = [rawString UTF8String]; [self updateEditStateWithDelta:flutter::TextEditingDelta(textBeforeChange, selectionBeforeChange.collapsed() ? composingBeforeChange diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm index 1202813a746e0..e3a203803d489 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm @@ -945,8 +945,8 @@ - (bool)testOperationsThatTriggerDelta { @"deltaText" : @"marked text", @"deltaStart" : @(14), @"deltaEnd" : @(14), - @"selectionBase" : @(25), - @"selectionExtent" : @(25), + @"selectionBase" : @(14), + @"selectionExtent" : @(15), @"selectionAffinity" : @"TextAffinity.upstream", @"selectionIsDirectional" : @(false), @"composingBase" : @(14), @@ -1030,7 +1030,7 @@ - (bool)testComposingWithDelta { @"deltaText" : @"m", @"deltaStart" : @(0), @"deltaEnd" : @(0), - @"selectionBase" : @(1), + @"selectionBase" : @(0), @"selectionExtent" : @(1), @"selectionAffinity" : @"TextAffinity.upstream", @"selectionIsDirectional" : @(false), @@ -1060,8 +1060,8 @@ - (bool)testComposingWithDelta { @"deltaText" : @"ma", @"deltaStart" : @(0), @"deltaEnd" : @(1), - @"selectionBase" : @(2), - @"selectionExtent" : @(2), + @"selectionBase" : @(0), + @"selectionExtent" : @(1), @"selectionAffinity" : @"TextAffinity.upstream", @"selectionIsDirectional" : @(false), @"composingBase" : @(0), @@ -1090,8 +1090,8 @@ - (bool)testComposingWithDelta { @"deltaText" : @"mar", @"deltaStart" : @(0), @"deltaEnd" : @(2), - @"selectionBase" : @(3), - @"selectionExtent" : @(3), + @"selectionBase" : @(0), + @"selectionExtent" : @(1), @"selectionAffinity" : @"TextAffinity.upstream", @"selectionIsDirectional" : @(false), @"composingBase" : @(0), @@ -1120,8 +1120,8 @@ - (bool)testComposingWithDelta { @"deltaText" : @"mark", @"deltaStart" : @(0), @"deltaEnd" : @(3), - @"selectionBase" : @(4), - @"selectionExtent" : @(4), + @"selectionBase" : @(0), + @"selectionExtent" : @(1), @"selectionAffinity" : @"TextAffinity.upstream", @"selectionIsDirectional" : @(false), @"composingBase" : @(0), @@ -1150,8 +1150,8 @@ - (bool)testComposingWithDelta { @"deltaText" : @"marke", @"deltaStart" : @(0), @"deltaEnd" : @(4), - @"selectionBase" : @(5), - @"selectionExtent" : @(5), + @"selectionBase" : @(0), + @"selectionExtent" : @(1), @"selectionAffinity" : @"TextAffinity.upstream", @"selectionIsDirectional" : @(false), @"composingBase" : @(0), @@ -1180,8 +1180,8 @@ - (bool)testComposingWithDelta { @"deltaText" : @"marked", @"deltaStart" : @(0), @"deltaEnd" : @(5), - @"selectionBase" : @(6), - @"selectionExtent" : @(6), + @"selectionBase" : @(0), + @"selectionExtent" : @(1), @"selectionAffinity" : @"TextAffinity.upstream", @"selectionIsDirectional" : @(false), @"composingBase" : @(0), diff --git a/shell/platform/windows/text_input_plugin.cc b/shell/platform/windows/text_input_plugin.cc index 9cf1229b5b8f5..f523bcbbf7e21 100644 --- a/shell/platform/windows/text_input_plugin.cc +++ b/shell/platform/windows/text_input_plugin.cc @@ -197,9 +197,7 @@ void TextInputPlugin::ComposeChangeHook(const std::u16string& text, std::string text_before_change = active_model_->GetText(); TextRange composing_before_change = active_model_->composing_range(); active_model_->AddText(text); - cursor_pos += active_model_->composing_range().start(); - active_model_->UpdateComposingText(text); - active_model_->SetSelection(TextRange(cursor_pos, cursor_pos)); + active_model_->UpdateComposingText(text, TextRange(cursor_pos, cursor_pos)); std::string text_after_change = active_model_->GetText(); if (enable_delta_model) { TextEditingDelta delta = TextEditingDelta(