From 1494075cb23dc67291471622aa4498e6585b08b6 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 25 Sep 2020 23:17:28 -0700 Subject: [PATCH 1/2] Add multi-step IME support to TextInputModel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This updates the platform-independent TextInputModel to add support for input method (abbreviated IM or IME) composing regions. In contrast to languages such as English, where keyboard input is managed keystroke-by-keystroke, languages such as Japanese require a multi-step input process wherein the user begins a composing sequence, during which point their keystrokes are captured by a system input method and converted into a text sequence. During composing, the user is able to edit the composing range and manage the conversion from keyboard input to text before eventually committing the text to the underlying text input field. To illustrate this, in Japanese, this sequence might look something like the following: 1. User types 'k'. The character 'k' is added to the composing region. Typically, the text 'k' will be inserted inline into the underlying text field but the composing range will be highlighted in some manner, frequently with a highlight or underline. 2. User types 'a'. The composing range is replaced with the phonetic kana character 'か' (ka). The composing range continues to be highlighted. 3. User types 'k'. The character 'k' is appended to the composing range such that the highlighted text is now 'かk' 4. User types 'u'. The trailing 'k' is replaced with the phonetic kana character 'く' (ku) such that the composing range now reads 'かく' The composing range continues to be highlighted. 5. The user presses the space bar to convert the kana characters to kanji. The composing range is replaced with '書く' (kaku: to write). 6. The user presses the space bar again to show other conversions. The user's configured input method (for example, ibus) pops up a completions menu populated with alternatives such as 各 (kaku: every), 描く (kaku: to draw), 核 (kaku: pit of a fruit, nucleus), 角 (kaku: angle), etc. 7. The user uses the arrow keys to navigate the completions menu and select the alternative to input. As they do, the inline composing region in the text field is updated. It continues to be highlighted or underlined. 8. The user hits enter to commit the composing region. The text is committed to the underlying text field and the visual highlighting is removed. 9. If the user presses another key, a new composing sequence begins. If a selection is present when composing begins, it is preserved until the first keypress of input is received, at which point the selection is deleted. If a composing sequence is aborted before the first keypress, the selection is preserved. Creating a new selection (with the mouse, for example) aborts composing and the composing region is automatically committed. A composing range and selection, both with an extent, are not permitted to co-exist. During composing, keyboard navigation via the arrow keys, or home and end (or equivalent shortcuts) is restricted to the composing range, as are deletions via backspace and the delete key. This patch adds two new private convenience methods, `editing_range` and `text_range`. The former returns the range for which editing is currently active -- the composing range, if composing, otherwise the full range of the text. The latter, returns a range from position 0 (inclusive) to `text_.length()` exclusive. --- shell/platform/common/cpp/text_input_model.cc | 119 ++- shell/platform/common/cpp/text_input_model.h | 90 +- .../common/cpp/text_input_model_unittests.cc | 932 +++++++++++++++++- 3 files changed, 1079 insertions(+), 62 deletions(-) diff --git a/shell/platform/common/cpp/text_input_model.cc b/shell/platform/common/cpp/text_input_model.cc index da0c1fe1e5ed8..c5bd998ba1be4 100644 --- a/shell/platform/common/cpp/text_input_model.cc +++ b/shell/platform/common/cpp/text_input_model.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/common/cpp/text_input_model.h" @@ -39,25 +38,88 @@ void TextInputModel::SetText(const std::string& text) { utf16_converter; text_ = utf16_converter.from_bytes(text); selection_ = TextRange(0); + composing_range_ = TextRange(0); } bool TextInputModel::SetSelection(const TextRange& range) { - if (!text_range().Contains(range)) { + if (composing_ && !range.collapsed()) { + return false; + } + if (!editable_range().Contains(range)) { return false; } selection_ = range; return true; } +bool TextInputModel::SetComposingRange(const TextRange& range, + size_t cursor_offset) { + if (!composing_ || !text_range().Contains(range)) { + return false; + } + composing_range_ = range; + selection_ = TextRange(range.start() + cursor_offset); + return true; +} + +void TextInputModel::BeginComposing() { + composing_ = true; + composing_range_ = TextRange(selection_.start()); +} + +void TextInputModel::UpdateComposingText(const std::string& composing_text) { + std::wstring_convert, char16_t> + utf16_converter; + std::u16string text = utf16_converter.from_bytes(composing_text); + + // 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); + SetComposingLength(text.length()); + selection_ = TextRange(composing_range_.end()); +} + +void TextInputModel::CommitComposing() { + // Preserve selection if no composing text was entered. + if (composing_range_.collapsed()) { + return; + } + composing_range_ = TextRange(composing_range_.end()); + selection_ = composing_range_; +} + +void TextInputModel::EndComposing() { + composing_ = false; + composing_range_ = TextRange(0); +} + bool TextInputModel::DeleteSelected() { if (selection_.collapsed()) { return false; } - text_.erase(selection_.start(), selection_.length()); - selection_ = TextRange(selection_.start()); + size_t start = selection_.start(); + text_.erase(start, selection_.length()); + selection_ = TextRange(start); + if (composing_) { + // This occurs only immediately after composing has begun with a selection. + composing_range_ = selection_; + } return true; } +void TextInputModel::SetComposingLength(size_t length) { + if (composing_range_.reversed()) { + size_t extent = composing_range_.extent(); + composing_range_ = TextRange(extent + length, extent); + } else { + size_t base = composing_range_.base(); + composing_range_ = TextRange(base, base + length); + } +} + void TextInputModel::AddCodePoint(char32_t c) { if (c <= 0xFFFF) { AddText(std::u16string({static_cast(c)})); @@ -74,6 +136,12 @@ void TextInputModel::AddCodePoint(char32_t c) { void TextInputModel::AddText(const std::u16string& text) { DeleteSelected(); + if (composing_) { + // Delete the current composing text, set the cursor to composing start. + text_.erase(composing_range_.start(), composing_range_.length()); + selection_ = TextRange(composing_range_.start()); + SetComposingLength(text.length()); + } size_t position = selection_.position(); text_.insert(position, text); selection_ = TextRange(position + text.length()); @@ -89,12 +157,15 @@ bool TextInputModel::Backspace() { if (DeleteSelected()) { return true; } - // If there's no selection, delete the preceding codepoint. + // There is no selection. Delete the preceding codepoint. size_t position = selection_.position(); - if (position != 0) { + if (position != editable_range().start()) { int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1; text_.erase(position - count, count); selection_ = TextRange(position - count); + if (composing_) { + SetComposingLength(composing_range_.length() - count); + } return true; } return false; @@ -104,36 +175,40 @@ bool TextInputModel::Delete() { if (DeleteSelected()) { return true; } - // If there's no selection, delete the preceding codepoint. + // There is no selection. Delete the preceding codepoint. size_t position = selection_.position(); - if (position != text_.length()) { + if (position < editable_range().end()) { int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1; text_.erase(position, count); + if (composing_) { + SetComposingLength(composing_range_.length() - count); + } return true; } return false; } bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) { + size_t max_pos = editable_range().end(); size_t start = selection_.extent(); if (offset_from_cursor < 0) { for (int i = 0; i < -offset_from_cursor; i++) { // If requested start is before the available text then reduce the // number of characters to delete. - if (start == 0) { + if (start == editable_range().start()) { count = i; break; } start -= IsTrailingSurrogate(text_.at(start - 1)) ? 2 : 1; } } else { - for (int i = 0; i < offset_from_cursor && start != text_.length(); i++) { + for (int i = 0; i < offset_from_cursor && start != max_pos; i++) { start += IsLeadingSurrogate(text_.at(start)) ? 2 : 1; } } auto end = start; - for (int i = 0; i < count && end != text_.length(); i++) { + for (int i = 0; i < count && end != max_pos; i++) { end += IsLeadingSurrogate(text_.at(start)) ? 2 : 1; } @@ -141,25 +216,33 @@ bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) { return false; } - text_.erase(start, end - start); + auto deleted_length = end - start; + text_.erase(start, deleted_length); // Cursor moves only if deleted area is before it. selection_ = TextRange(offset_from_cursor <= 0 ? start : selection_.start()); + // Adjust composing range. + if (composing_) { + SetComposingLength(composing_range_.length() - deleted_length); + } return true; } bool TextInputModel::MoveCursorToBeginning() { - if (selection_.collapsed() && selection_.position() == 0) + size_t min_pos = editable_range().start(); + if (selection_.collapsed() && selection_.position() == min_pos) { return false; - selection_ = TextRange(0); + } + selection_ = TextRange(min_pos); return true; } bool TextInputModel::MoveCursorToEnd() { - size_t max_pos = text_.length(); - if (selection_.collapsed() && selection_.position() == max_pos) + size_t max_pos = editable_range().end(); + if (selection_.collapsed() && selection_.position() == max_pos) { return false; + } selection_ = TextRange(max_pos); return true; } @@ -172,7 +255,7 @@ bool TextInputModel::MoveCursorForward() { } // Otherwise, move the cursor forward. size_t position = selection_.position(); - if (position != text_.length()) { + if (position != editable_range().end()) { int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1; selection_ = TextRange(position + count); return true; @@ -188,7 +271,7 @@ bool TextInputModel::MoveCursorBack() { } // Otherwise, move the cursor backward. size_t position = selection_.position(); - if (position != 0) { + if (position != editable_range().start()) { int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1; selection_ = TextRange(position - count); return true; diff --git a/shell/platform/common/cpp/text_input_model.h b/shell/platform/common/cpp/text_input_model.h index c49f786f0a9c0..97485d01a0f23 100644 --- a/shell/platform/common/cpp/text_input_model.h +++ b/shell/platform/common/cpp/text_input_model.h @@ -28,8 +28,43 @@ class TextInputModel { // Attempts to set the text selection. // // Returns false if the selection is not within the bounds of the text. + // While in composing mode, the selection is restricted to the composing + // range; otherwise, it is restricted to the length of the text. bool SetSelection(const TextRange& range); + // Attempts to set the composing range. + // + // Returns false if the range or offset are out of range for the text, or if + // the offset is outside the composing range. + bool SetComposingRange(const TextRange& range, size_t cursor_offset); + + // Begins IME composing mode. + // + // Resets the composing base and extent to the selection start. The existing + // selection is preserved in case composing is aborted with no changes. Until + // |EndComposing| is called, any further changes to selection base and extent + // are restricted to the composing range. + void BeginComposing(); + + // Replaces the composing range with new text. + // + // 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 + // |composing_text| and the selection base and offset are set to the end of + // the composing range. + void UpdateComposingText(const std::string& composing_text); + + // Commits composing range to the string. + // + // Causes the composing base and extent to be collapsed to the end of the + // range. + void CommitComposing(); + + // Ends IME composing mode. + // + // Collapses the composing base and offset to 0. + void EndComposing(); + // Adds a Unicode code point. // // Either appends after the cursor (when selection base and extent are the @@ -52,18 +87,21 @@ class TextInputModel { // Deletes either the selection, or one character ahead of the cursor. // // Deleting one character ahead of the cursor occurs when the selection base - // and extent are the same. + // and extent are the same. When composing is active, deletions are + // restricted to text between the composing base and extent. // // Returns true if any deletion actually occurred. bool Delete(); // Deletes text near the cursor. // - // A section is made starting at @offset code points past the cursor (negative - // values go before the cursor). @count code points are removed. The selection - // may go outside the bounds of the text and will result in only the part - // selection that covers the available text being deleted. The existing - // selection is ignored and removed after this operation. + // A section is made starting at |offset_from_cursor| code points past the + // cursor (negative values go before the cursor). |count| code points are + // removed. The selection may go outside the bounds of the available text and + // will result in only the part selection that covers the available text + // being deleted. The existing selection is ignored and removed after this + // operation. When composing is active, deletions are restricted to the + // composing range. // // Returns true if any deletion actually occurred. bool DeleteSurrounding(int offset_from_cursor, int count); @@ -71,7 +109,8 @@ class TextInputModel { // Deletes either the selection, or one character behind the cursor. // // Deleting one character behind the cursor occurs when the selection base - // and extent are the same. + // and extent are the same. When composing is active, deletions are + // restricted to the text between the composing base and extent. // // Returns true if any deletion actually occurred. bool Backspace(); @@ -79,21 +118,31 @@ class TextInputModel { // Attempts to move the cursor backward. // // Returns true if the cursor could be moved. If a selection is active, moves - // to the start of the selection. + // to the start of the selection. If composing is active, motion is + // restricted to the composing range. bool MoveCursorBack(); // Attempts to move the cursor forward. // // Returns true if the cursor could be moved. If a selection is active, moves - // to the end of the selection. + // to the end of the selection. If composing is active, motion is restricted + // to the composing range. bool MoveCursorForward(); // Attempts to move the cursor to the beginning. // + // If composing is active, the cursor is moved to the beginning of the + // composing range; otherwise, it is moved to the beginning of the text. If + // composing is active, motion is restricted to the composing range. + // // Returns true if the cursor could be moved. bool MoveCursorToBeginning(); - // Attempts to move the cursor to the back. + // Attempts to move the cursor to the end. + // + // If composing is active, the cursor is moved to the end of the composing + // range; otherwise, it is moved to the end of the text. If composing is + // active, motion is restricted to the composing range. // // Returns true if the cursor could be moved. bool MoveCursorToEnd(); @@ -108,6 +157,14 @@ class TextInputModel { // The current selection. TextRange selection() const { return selection_; } + // The composing range. + // + // If not in composing mode, returns a collapsed range at position 0. + TextRange composing_range() const { return composing_range_; } + + // Whether multi-step input composing mode is active. + bool composing() const { return composing_; } + private: // Deletes the current selection, if any. // @@ -115,11 +172,24 @@ class TextInputModel { // reset to the start of the selected range. bool DeleteSelected(); + // Adjusts the composing range to |length|. + void SetComposingLength(size_t length); + + // Returns the currently editable text range. + // + // In composing mode, returns the composing range; otherwise, returns a range + // covering the entire text. + TextRange editable_range() const { + return composing_ ? composing_range_ : text_range(); + } + // Returns a range covering the entire text. TextRange text_range() const { return TextRange(0, text_.length()); } std::u16string text_; TextRange selection_ = TextRange(0); + TextRange composing_range_ = TextRange(0); + bool composing_ = false; }; } // namespace flutter diff --git a/shell/platform/common/cpp/text_input_model_unittests.cc b/shell/platform/common/cpp/text_input_model_unittests.cc index 730f2b9905428..6ee80206295eb 100644 --- a/shell/platform/common/cpp/text_input_model_unittests.cc +++ b/shell/platform/common/cpp/text_input_model_unittests.cc @@ -52,6 +52,18 @@ TEST(TextInputModel, SetSelectionStart) { model->SetText("ABCDE"); EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetSelectionComposingStart) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->SetSelection(TextRange(1))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -60,14 +72,38 @@ TEST(TextInputModel, SetSelectionMiddle) { model->SetText("ABCDE"); EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetSelectionComposingMiddle) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->SetSelection(TextRange(2))); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, SetSelectionEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetSelectionComposingEnd) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->SetSelection(TextRange(4))); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -76,6 +112,18 @@ TEST(TextInputModel, SetSelectionWthExtent) { model->SetText("ABCDE"); EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_EQ(model->selection(), TextRange(1, 4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetSelectionWthExtentComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_FALSE(model->SetSelection(TextRange(1, 4))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -84,6 +132,18 @@ TEST(TextInputModel, SetSelectionReverseExtent) { model->SetText("ABCDE"); EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_EQ(model->selection(), TextRange(4, 1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetSelectionReverseExtentComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_FALSE(model->SetSelection(TextRange(4, 1))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -92,7 +152,255 @@ TEST(TextInputModel, SetSelectionOutsideString) { model->SetText("ABCDE"); EXPECT_FALSE(model->SetSelection(TextRange(4, 6))); EXPECT_FALSE(model->SetSelection(TextRange(5, 6))); - EXPECT_FALSE(model->SetSelection(TextRange(6, 6))); + EXPECT_FALSE(model->SetSelection(TextRange(6))); +} + +TEST(TextInputModel, SetSelectionOutsideComposingRange) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_FALSE(model->SetSelection(TextRange(0))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_FALSE(model->SetSelection(TextRange(5))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); +} + +TEST(TextInputModel, SetComposingRangeStart) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(0, 0), 0)); + EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetComposingRangeMiddle) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(2, 2), 0)); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(2)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetComposingRangeEnd) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(5, 5), 0)); + EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(5)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetComposingRangeWithExtent) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetComposingRangeReverseExtent) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetComposingRangeOutsideString) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_FALSE(model->SetComposingRange(TextRange(4, 6), 0)); + EXPECT_FALSE(model->SetComposingRange(TextRange(5, 6), 0)); + EXPECT_FALSE(model->SetComposingRange(TextRange(6, 6), 0)); +} + +// Composing sequence with no initial selection and no text input. +TEST(TextInputModel, CommitComposingNoTextWithNoSelection) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->SetSelection(TextRange(0)); + + // Verify no changes on BeginComposing. + model->BeginComposing(); + EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify no changes on CommitComposing. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify no changes on CommitComposing. + model->EndComposing(); + EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +// Composing sequence with an initial selection and no text input. +TEST(TextInputModel, CommitComposingNoTextWithSelection) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->SetSelection(TextRange(1, 3)); + + // Verify no changes on BeginComposing. + model->BeginComposing(); + EXPECT_EQ(model->selection(), TextRange(1, 3)); + EXPECT_EQ(model->composing_range(), TextRange(1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify no changes on CommitComposing. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(1, 3)); + EXPECT_EQ(model->composing_range(), TextRange(1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify no changes on CommitComposing. + model->EndComposing(); + EXPECT_EQ(model->selection(), TextRange(1, 3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +// Composing sequence with no initial selection. +TEST(TextInputModel, CommitComposingTextWithNoSelection) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->SetSelection(TextRange(1)); + + // Verify no changes on BeginComposing. + model->BeginComposing(); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify selection base, extent and composing extent increment as text is + // entered. Verify composing base does not change. + model->UpdateComposingText("つ"); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "AつBCDE"); + model->UpdateComposingText("つる"); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "AつるBCDE"); + + // Verify that cursor position is set to correct offset from composing base. + model->UpdateComposingText("鶴"); + EXPECT_TRUE(model->SetSelection(TextRange(1))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴BCDE"); + + // Verify composing base is set to composing extent on commit. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(2)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴BCDE"); + + // Verify that further text entry increments the selection base, extent and + // the composing extent. Verify that composing base does not change. + model->UpdateComposingText("が"); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(2, 3)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がBCDE"); + + // Verify composing base is set to composing extent on commit. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(3)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がBCDE"); + + // Verify no changes on EndComposing. + model->EndComposing(); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がBCDE"); +} + +// Composing sequence with an initial selection. +TEST(TextInputModel, CommitComposingTextWithSelection) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->SetSelection(TextRange(1, 3)); + + // Verify no changes on BeginComposing. + model->BeginComposing(); + EXPECT_EQ(model->selection(), TextRange(1, 3)); + EXPECT_EQ(model->composing_range(), TextRange(1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify selection is replaced and selection base, extent and composing + // extent increment to the position immediately after the composing text. + // Verify composing base does not change. + model->UpdateComposingText("つ"); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "AつDE"); + + // Verify that further text entry increments the selection base, extent and + // the composing extent. Verify that composing base does not change. + model->UpdateComposingText("つる"); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "AつるDE"); + + // Verify that cursor position is set to correct offset from composing base. + model->UpdateComposingText("鶴"); + EXPECT_TRUE(model->SetSelection(TextRange(1))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴DE"); + + // Verify composing base is set to composing extent on commit. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(2)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴DE"); + + // Verify that further text entry increments the selection base, extent and + // the composing extent. Verify that composing base does not change. + model->UpdateComposingText("が"); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(2, 3)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がDE"); + + // Verify composing base is set to composing extent on commit. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(3)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がDE"); + + // Verify no changes on EndComposing. + model->EndComposing(); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がDE"); +} + +TEST(TextInputModel, UpdateComposingRemovesLastComposingCharacter) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + model->SetComposingRange(TextRange(1, 2), 1); + model->UpdateComposingText(""); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1)); + model->SetText("ACDE"); } TEST(TextInputModel, AddCodePoint) { @@ -103,6 +411,7 @@ TEST(TextInputModel, AddCodePoint) { model->AddCodePoint('D'); model->AddCodePoint('E'); EXPECT_EQ(model->selection(), TextRange(6)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AB😄DE"); } @@ -112,6 +421,7 @@ TEST(TextInputModel, AddCodePointSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddCodePoint('x'); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AxE"); } @@ -121,6 +431,7 @@ TEST(TextInputModel, AddCodePointReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddCodePoint('x'); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AxE"); } @@ -130,6 +441,7 @@ TEST(TextInputModel, AddCodePointSelectionWideCharacter) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddCodePoint(0x1f604); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "A😄E"); } @@ -139,6 +451,7 @@ TEST(TextInputModel, AddCodePointReverseSelectionWideCharacter) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddCodePoint(0x1f604); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "A😄E"); } @@ -148,6 +461,7 @@ TEST(TextInputModel, AddText) { model->AddText("😄"); model->AddText("FGHIJ"); EXPECT_EQ(model->selection(), TextRange(12)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE😄FGHIJ"); } @@ -157,6 +471,7 @@ TEST(TextInputModel, AddTextSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddText("xy"); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AxyE"); } @@ -166,6 +481,7 @@ TEST(TextInputModel, AddTextReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddText("xy"); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AxyE"); } @@ -175,6 +491,7 @@ TEST(TextInputModel, AddTextSelectionWideCharacter) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddText(u"😄🙃"); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "A😄🙃E"); } @@ -184,42 +501,47 @@ TEST(TextInputModel, AddTextReverseSelectionWideCharacter) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddText(u"😄🙃"); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "A😄🙃E"); } TEST(TextInputModel, DeleteStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "BCDE"); } TEST(TextInputModel, DeleteMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABDE"); } TEST(TextInputModel, DeleteEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); ASSERT_FALSE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, DeleteWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); + EXPECT_TRUE(model->SetSelection(TextRange(4))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "😄🙃🧐"); } @@ -229,6 +551,7 @@ TEST(TextInputModel, DeleteSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } @@ -238,96 +561,276 @@ TEST(TextInputModel, DeleteReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } +TEST(TextInputModel, DeleteStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + ASSERT_TRUE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ACDE"); +} + +TEST(TextInputModel, DeleteStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + ASSERT_TRUE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(3, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ACDE"); +} + +TEST(TextInputModel, DeleteMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + ASSERT_TRUE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ABDE"); +} + +TEST(TextInputModel, DeleteMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + ASSERT_TRUE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(3, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABDE"); +} + +TEST(TextInputModel, DeleteEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + ASSERT_FALSE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, DeleteEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + ASSERT_FALSE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + TEST(TextInputModel, DeleteSurroundingAtCursor) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); + EXPECT_TRUE(model->DeleteSurrounding(0, 1)); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABDE"); +} + +TEST(TextInputModel, DeleteSurroundingAtCursorComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); EXPECT_TRUE(model->DeleteSurrounding(0, 1)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); EXPECT_STREQ(model->GetText().c_str(), "ABDE"); } TEST(TextInputModel, DeleteSurroundingAtCursorAll) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(0, 3)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AB"); } +TEST(TextInputModel, DeleteSurroundingAtCursorAllComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->DeleteSurrounding(0, 2)); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ABE"); +} + TEST(TextInputModel, DeleteSurroundingAtCursorGreedy) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(0, 4)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AB"); } +TEST(TextInputModel, DeleteSurroundingAtCursorGreedyComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->DeleteSurrounding(0, 4)); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ABE"); +} + TEST(TextInputModel, DeleteSurroundingBeforeCursor) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(-1, 1)); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ACDE"); } +TEST(TextInputModel, DeleteSurroundingBeforeCursorComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 2)); + EXPECT_TRUE(model->DeleteSurrounding(-1, 1)); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ABDE"); +} + TEST(TextInputModel, DeleteSurroundingBeforeCursorAll) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(-2, 2)); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "CDE"); } +TEST(TextInputModel, DeleteSurroundingBeforeCursorAllComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 2)); + EXPECT_TRUE(model->DeleteSurrounding(-2, 2)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ADE"); +} + TEST(TextInputModel, DeleteSurroundingBeforeCursorGreedy) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(-3, 3)); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "CDE"); } +TEST(TextInputModel, DeleteSurroundingBeforeCursorGreedyComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 2)); + EXPECT_TRUE(model->DeleteSurrounding(-3, 3)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ADE"); +} + TEST(TextInputModel, DeleteSurroundingAfterCursor) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(1, 1)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCE"); } +TEST(TextInputModel, DeleteSurroundingAfterCursorComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->DeleteSurrounding(1, 1)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ABDE"); +} + TEST(TextInputModel, DeleteSurroundingAfterCursorAll) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(1, 2)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABC"); } +TEST(TextInputModel, DeleteSurroundingAfterCursorAllComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->DeleteSurrounding(1, 2)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ABE"); +} + TEST(TextInputModel, DeleteSurroundingAfterCursorGreedy) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(1, 3)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABC"); } +TEST(TextInputModel, DeleteSurroundingAfterCursorGreedyComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->DeleteSurrounding(1, 3)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ABE"); +} + TEST(TextInputModel, DeleteSurroundingSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); EXPECT_TRUE(model->SetSelection(TextRange(2, 3))); EXPECT_TRUE(model->DeleteSurrounding(0, 1)); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCE"); } @@ -337,42 +840,47 @@ TEST(TextInputModel, DeleteSurroundingReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 3))); EXPECT_TRUE(model->DeleteSurrounding(0, 1)); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCE"); } TEST(TextInputModel, BackspaceStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); ASSERT_FALSE(model->Backspace()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, BackspaceMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); ASSERT_TRUE(model->Backspace()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ACDE"); } TEST(TextInputModel, BackspaceEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); ASSERT_TRUE(model->Backspace()); EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCD"); } TEST(TextInputModel, BackspaceWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); + EXPECT_TRUE(model->SetSelection(TextRange(4))); ASSERT_TRUE(model->Backspace()); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "😄🤪🧐"); } @@ -382,6 +890,7 @@ TEST(TextInputModel, BackspaceSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } @@ -391,42 +900,113 @@ TEST(TextInputModel, BackspaceReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } +TEST(TextInputModel, BackspaceStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + ASSERT_FALSE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, BackspaceStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0)); + ASSERT_FALSE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, BackspaceMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + ASSERT_TRUE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ACDE"); +} + +TEST(TextInputModel, BackspaceMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + ASSERT_TRUE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(3, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ACDE"); +} + +TEST(TextInputModel, BackspaceEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + ASSERT_TRUE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ABCE"); +} + +TEST(TextInputModel, BackspaceEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + ASSERT_TRUE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(3, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCE"); +} + TEST(TextInputModel, MoveCursorForwardStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorForwardMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorForwardEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); EXPECT_FALSE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorForwardWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); + EXPECT_TRUE(model->SetSelection(TextRange(4))); ASSERT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(6)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "😄🙃🤪🧐"); } @@ -436,6 +1016,7 @@ TEST(TextInputModel, MoveCursorForwardSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -445,42 +1026,113 @@ TEST(TextInputModel, MoveCursorForwardReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0)); + EXPECT_TRUE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + EXPECT_TRUE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + EXPECT_FALSE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + EXPECT_FALSE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_FALSE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); EXPECT_TRUE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); + EXPECT_TRUE(model->SetSelection(TextRange(4))); ASSERT_TRUE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "😄🙃🤪🧐"); } @@ -490,6 +1142,7 @@ TEST(TextInputModel, MoveCursorBackSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -499,33 +1152,105 @@ TEST(TextInputModel, MoveCursorBackReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->SetSelection(TextRange(1))); + EXPECT_FALSE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0)); + EXPECT_TRUE(model->SetSelection(TextRange(1))); + EXPECT_FALSE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + EXPECT_TRUE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + EXPECT_TRUE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + EXPECT_TRUE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_FALSE(model->MoveCursorToBeginning()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->MoveCursorToBeginning()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); EXPECT_TRUE(model->MoveCursorToBeginning()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -535,6 +1260,7 @@ TEST(TextInputModel, MoveCursorToBeginningSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorToBeginning()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -544,33 +1270,103 @@ TEST(TextInputModel, MoveCursorToBeginningReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorToBeginning()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_FALSE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0)); + EXPECT_FALSE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + EXPECT_TRUE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + EXPECT_TRUE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + EXPECT_TRUE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_TRUE(model->MoveCursorToEnd()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->MoveCursorToEnd()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); EXPECT_FALSE(model->MoveCursorToEnd()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -580,6 +1376,7 @@ TEST(TextInputModel, MoveCursorToEndSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorToEnd()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -589,6 +1386,73 @@ TEST(TextInputModel, MoveCursorToEndReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorToEnd()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + EXPECT_TRUE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + EXPECT_FALSE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + EXPECT_FALSE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -596,7 +1460,7 @@ TEST(TextInputModel, GetCursorOffset) { auto model = std::make_unique(); // These characters take 1, 2, 3 and 4 bytes in UTF-8. model->SetText("$¢€𐍈"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_EQ(model->GetCursorOffset(), 0); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->GetCursorOffset(), 1); From 37b06c41b94f92faccd654ac193225aaca51a38f Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 15 Oct 2020 11:25:58 -0700 Subject: [PATCH 2/2] Move SetComposingLength to TextRange::set_* Adds set_base, set_extent, set_start, set_end methods to TextRange. --- shell/platform/common/cpp/text_input_model.cc | 20 ++----- shell/platform/common/cpp/text_input_model.h | 3 - shell/platform/common/cpp/text_range.h | 36 ++++++++++-- .../common/cpp/text_range_unittests.cc | 56 +++++++++++++++++++ 4 files changed, 91 insertions(+), 24 deletions(-) diff --git a/shell/platform/common/cpp/text_input_model.cc b/shell/platform/common/cpp/text_input_model.cc index c5bd998ba1be4..649baede20f51 100644 --- a/shell/platform/common/cpp/text_input_model.cc +++ b/shell/platform/common/cpp/text_input_model.cc @@ -78,7 +78,7 @@ void TextInputModel::UpdateComposingText(const std::string& composing_text) { } DeleteSelected(); text_.replace(composing_range_.start(), composing_range_.length(), text); - SetComposingLength(text.length()); + composing_range_.set_end(composing_range_.start() + text.length()); selection_ = TextRange(composing_range_.end()); } @@ -110,16 +110,6 @@ bool TextInputModel::DeleteSelected() { return true; } -void TextInputModel::SetComposingLength(size_t length) { - if (composing_range_.reversed()) { - size_t extent = composing_range_.extent(); - composing_range_ = TextRange(extent + length, extent); - } else { - size_t base = composing_range_.base(); - composing_range_ = TextRange(base, base + length); - } -} - void TextInputModel::AddCodePoint(char32_t c) { if (c <= 0xFFFF) { AddText(std::u16string({static_cast(c)})); @@ -140,7 +130,7 @@ void TextInputModel::AddText(const std::u16string& text) { // Delete the current composing text, set the cursor to composing start. text_.erase(composing_range_.start(), composing_range_.length()); selection_ = TextRange(composing_range_.start()); - SetComposingLength(text.length()); + composing_range_.set_end(composing_range_.start() + text.length()); } size_t position = selection_.position(); text_.insert(position, text); @@ -164,7 +154,7 @@ bool TextInputModel::Backspace() { text_.erase(position - count, count); selection_ = TextRange(position - count); if (composing_) { - SetComposingLength(composing_range_.length() - count); + composing_range_.set_end(composing_range_.end() - count); } return true; } @@ -181,7 +171,7 @@ bool TextInputModel::Delete() { int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1; text_.erase(position, count); if (composing_) { - SetComposingLength(composing_range_.length() - count); + composing_range_.set_end(composing_range_.end() - count); } return true; } @@ -224,7 +214,7 @@ bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) { // Adjust composing range. if (composing_) { - SetComposingLength(composing_range_.length() - deleted_length); + composing_range_.set_end(composing_range_.end() - deleted_length); } return true; } diff --git a/shell/platform/common/cpp/text_input_model.h b/shell/platform/common/cpp/text_input_model.h index 97485d01a0f23..a9560b7b1483d 100644 --- a/shell/platform/common/cpp/text_input_model.h +++ b/shell/platform/common/cpp/text_input_model.h @@ -172,9 +172,6 @@ class TextInputModel { // reset to the start of the selected range. bool DeleteSelected(); - // Adjusts the composing range to |length|. - void SetComposingLength(size_t length); - // Returns the currently editable text range. // // In composing mode, returns the composing range; otherwise, returns a range diff --git a/shell/platform/common/cpp/text_range.h b/shell/platform/common/cpp/text_range.h index 60482da5158ac..d951c45f98e6e 100644 --- a/shell/platform/common/cpp/text_range.h +++ b/shell/platform/common/cpp/text_range.h @@ -21,19 +21,43 @@ class TextRange { virtual ~TextRange() = default; - // Returns the base position of the range. + // The base position of the range. size_t base() const { return base_; } - // Returns the extent position of the range. + // Sets the base position of the range. + void set_base(size_t pos) { base_ = pos; } + + // The extent position of the range. size_t extent() const { return extent_; } - // Returns the lesser of the base and extent positions. + // Sets the extent position of the range. + void set_extent(size_t pos) { extent_ = pos; } + + // The lesser of the base and extent positions. size_t start() const { return std::min(base_, extent_); } - // Returns the greater of the base and extent positions. + // Sets the start position of the range. + void set_start(size_t pos) { + if (base_ <= extent_) { + base_ = pos; + } else { + extent_ = pos; + } + } + + // The greater of the base and extent positions. size_t end() const { return std::max(base_, extent_); } - // Returns the position of a collapsed range. + // Sets the end position of the range. + void set_end(size_t pos) { + if (base_ <= extent_) { + extent_ = pos; + } else { + base_ = pos; + } + } + + // The position of a collapsed range. // // Asserts that the range is of length 0. size_t position() const { @@ -41,7 +65,7 @@ class TextRange { return extent_; } - // Returns the length of the range. + // The length of the range. size_t length() const { return end() - start(); } // Returns true if the range is of length 0. diff --git a/shell/platform/common/cpp/text_range_unittests.cc b/shell/platform/common/cpp/text_range_unittests.cc index eaf30d935a515..9aecf34309b98 100644 --- a/shell/platform/common/cpp/text_range_unittests.cc +++ b/shell/platform/common/cpp/text_range_unittests.cc @@ -50,6 +50,62 @@ TEST(TextRange, TextRangeFromReversedRange) { EXPECT_FALSE(range.collapsed()); } +TEST(TextRange, SetBase) { + TextRange range(3, 7); + range.set_base(4); + EXPECT_EQ(range.base(), size_t(4)); + EXPECT_EQ(range.extent(), size_t(7)); +} + +TEST(TextRange, SetBaseReversed) { + TextRange range(7, 3); + range.set_base(5); + EXPECT_EQ(range.base(), size_t(5)); + EXPECT_EQ(range.extent(), size_t(3)); +} + +TEST(TextRange, SetExtent) { + TextRange range(3, 7); + range.set_extent(6); + EXPECT_EQ(range.base(), size_t(3)); + EXPECT_EQ(range.extent(), size_t(6)); +} + +TEST(TextRange, SetExtentReversed) { + TextRange range(7, 3); + range.set_extent(4); + EXPECT_EQ(range.base(), size_t(7)); + EXPECT_EQ(range.extent(), size_t(4)); +} + +TEST(TextRange, SetStart) { + TextRange range(3, 7); + range.set_start(5); + EXPECT_EQ(range.base(), size_t(5)); + EXPECT_EQ(range.extent(), size_t(7)); +} + +TEST(TextRange, SetStartReversed) { + TextRange range(7, 3); + range.set_start(5); + EXPECT_EQ(range.base(), size_t(7)); + EXPECT_EQ(range.extent(), size_t(5)); +} + +TEST(TextRange, SetEnd) { + TextRange range(3, 7); + range.set_end(6); + EXPECT_EQ(range.base(), size_t(3)); + EXPECT_EQ(range.extent(), size_t(6)); +} + +TEST(TextRange, SetEndReversed) { + TextRange range(7, 3); + range.set_end(5); + EXPECT_EQ(range.base(), size_t(5)); + EXPECT_EQ(range.extent(), size_t(3)); +} + TEST(TextRange, ContainsPreStartPosition) { TextRange range(2, 6); EXPECT_FALSE(range.Contains(1));