Implement surrounding text API for exo::TextInput
Bug: 826614
Test: exo_unittests
Change-Id: Ia2fe9a39c38b0ea93d1f0a0f5aa820536a656a08
Reviewed-on: https://chromium-review.googlesource.com/c/1399722
Commit-Queue: Jun Mukai <mukai@chromium.org>
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Reviewed-by: Yuichiro Hanada <yhanada@chromium.org>
Cr-Commit-Position: refs/heads/master@{#629807}
diff --git a/components/exo/text_input.cc b/components/exo/text_input.cc
index f2534c4c..02fb7dcd 100644
--- a/components/exo/text_input.cc
+++ b/components/exo/text_input.cc
@@ -4,6 +4,9 @@
#include "components/exo/text_input.h"
+#include <algorithm>
+
+#include "base/strings/utf_string_conversions.h"
#include "components/exo/surface.h"
#include "components/exo/wm_helper.h"
#include "third_party/icu/source/common/unicode/uchar.h"
@@ -25,6 +28,14 @@
} // namespace
+size_t OffsetFromUTF8Offset(const base::StringPiece& text, uint32_t offset) {
+ return base::UTF8ToUTF16(text.substr(0, offset)).size();
+}
+
+size_t OffsetFromUTF16Offset(const base::StringPiece16& text, uint32_t offset) {
+ return base::UTF16ToUTF8(text.substr(0, offset)).size();
+}
+
TextInput::TextInput(std::unique_ptr<Delegate> delegate)
: delegate_(std::move(delegate)) {}
@@ -69,8 +80,14 @@
}
void TextInput::SetSurroundingText(const base::string16& text,
- uint32_t cursor_pos) {
- NOTIMPLEMENTED();
+ uint32_t cursor_pos,
+ uint32_t anchor) {
+ surrounding_text_ = text;
+ cursor_pos_ = gfx::Range(cursor_pos);
+ if (anchor < cursor_pos)
+ cursor_pos_->set_start(anchor);
+ else
+ cursor_pos_->set_end(anchor);
}
void TextInput::SetTypeModeFlags(ui::TextInputType type,
@@ -163,37 +180,87 @@
}
bool TextInput::GetTextRange(gfx::Range* range) const {
- NOTIMPLEMENTED_LOG_ONCE();
- return false;
+ if (!cursor_pos_)
+ return false;
+ range->set_start(0);
+ if (composition_.text.empty()) {
+ range->set_end(surrounding_text_.size());
+ } else {
+ range->set_end(surrounding_text_.size() - cursor_pos_->length() +
+ composition_.text.size());
+ }
+ return true;
}
bool TextInput::GetCompositionTextRange(gfx::Range* range) const {
- NOTIMPLEMENTED_LOG_ONCE();
- return false;
+ if (!cursor_pos_ || composition_.text.empty())
+ return false;
+
+ range->set_start(cursor_pos_->start());
+ range->set_end(cursor_pos_->start() + composition_.text.size());
+ return true;
}
bool TextInput::GetEditableSelectionRange(gfx::Range* range) const {
- NOTIMPLEMENTED_LOG_ONCE();
- return false;
+ if (!cursor_pos_)
+ return false;
+ range->set_start(cursor_pos_->start());
+ range->set_end(cursor_pos_->end());
+ return true;
}
bool TextInput::SetEditableSelectionRange(const gfx::Range& range) {
- NOTIMPLEMENTED_LOG_ONCE();
- return false;
+ if (surrounding_text_.size() < range.GetMax())
+ return false;
+ delegate_->SetCursor(
+ gfx::Range(OffsetFromUTF16Offset(surrounding_text_, range.start()),
+ OffsetFromUTF16Offset(surrounding_text_, range.end())));
+ return true;
}
bool TextInput::DeleteRange(const gfx::Range& range) {
- // TODO(mukai): call delegate_->DeleteSurroundingText(range) once it's
- // supported.
- NOTIMPLEMENTED_LOG_ONCE();
- return false;
+ if (surrounding_text_.size() < range.GetMax())
+ return false;
+ delegate_->DeleteSurroundingText(
+ gfx::Range(OffsetFromUTF16Offset(surrounding_text_, range.start()),
+ OffsetFromUTF16Offset(surrounding_text_, range.end())));
+ return true;
}
bool TextInput::GetTextFromRange(const gfx::Range& range,
base::string16* text) const {
- // TODO(mukai): support of surrounding text.
- NOTIMPLEMENTED_LOG_ONCE();
- return false;
+ gfx::Range text_range;
+ if (!GetTextRange(&text_range) || !text_range.Contains(range))
+ return false;
+ if (composition_.text.empty() || range.GetMax() <= cursor_pos_->GetMin()) {
+ text->assign(surrounding_text_, range.GetMin(), range.length());
+ return true;
+ }
+ size_t composition_end = cursor_pos_->GetMin() + composition_.text.size();
+ if (range.GetMin() >= composition_end) {
+ size_t start =
+ range.GetMin() - composition_.text.size() + cursor_pos_->length();
+ text->assign(surrounding_text_, start, range.length());
+ return true;
+ }
+
+ size_t start_in_composition = 0;
+ if (range.GetMin() <= cursor_pos_->GetMin()) {
+ text->assign(surrounding_text_, range.GetMin(),
+ cursor_pos_->GetMin() - range.GetMin());
+ } else {
+ start_in_composition = range.GetMin() - cursor_pos_->GetMin();
+ }
+ if (range.GetMax() <= composition_end) {
+ text->append(composition_.text, start_in_composition,
+ range.GetMax() - cursor_pos_->GetMin() - start_in_composition);
+ } else {
+ text->append(composition_.text, start_in_composition,
+ composition_.text.size() - start_in_composition);
+ text->append(surrounding_text_, cursor_pos_->GetMax(),
+ range.GetMax() - composition_end);
+ }
+ return true;
}
void TextInput::OnInputMethodChanged() {
@@ -214,7 +281,17 @@
return true;
}
-void TextInput::ExtendSelectionAndDelete(size_t before, size_t after) {}
+void TextInput::ExtendSelectionAndDelete(size_t before, size_t after) {
+ if (!cursor_pos_)
+ return;
+ uint32_t start =
+ (cursor_pos_->GetMin() < before) ? 0 : (cursor_pos_->GetMin() - before);
+ uint32_t end =
+ std::min(cursor_pos_->GetMax() + after, surrounding_text_.size());
+ delegate_->DeleteSurroundingText(
+ gfx::Range(OffsetFromUTF16Offset(surrounding_text_, start),
+ OffsetFromUTF16Offset(surrounding_text_, end)));
+}
void TextInput::EnsureCaretNotInRect(const gfx::Rect& rect) {}
diff --git a/components/exo/text_input.h b/components/exo/text_input.h
index 4a47440..b00873a 100644
--- a/components/exo/text_input.h
+++ b/components/exo/text_input.h
@@ -23,6 +23,9 @@
namespace exo {
class Surface;
+size_t OffsetFromUTF8Offset(const base::StringPiece& text, uint32_t offset);
+size_t OffsetFromUTF16Offset(const base::StringPiece16& text, uint32_t offset);
+
// This class bridges the ChromeOS input method and a text-input context.
class TextInput : public ui::TextInputClient,
public keyboard::KeyboardControllerObserver {
@@ -47,10 +50,11 @@
// Commit |text| to the current text input session.
virtual void Commit(const base::string16& text) = 0;
- // Set the cursor position.
+ // Set the cursor position. The range should be in bytes offset.
virtual void SetCursor(const gfx::Range& selection) = 0;
- // Delete the surrounding text of the current text input.
+ // Delete the surrounding text of the current text input. The range should
+ // be in the bytes offset.
virtual void DeleteSurroundingText(const gfx::Range& range) = 0;
// Sends a key event.
@@ -83,7 +87,9 @@
void Resync();
// Sets the surrounding text in the app.
- void SetSurroundingText(const base::string16& text, uint32_t cursor_pos);
+ void SetSurroundingText(const base::string16& text,
+ uint32_t cursor_pos,
+ uint32_t anchor);
// Sets the text input type, mode, flags, and |should_do_learning|.
void SetTypeModeFlags(ui::TextInputType type,
@@ -148,6 +154,8 @@
int flags_ = ui::TEXT_INPUT_FLAG_NONE;
bool should_do_learning_ = true;
ui::CompositionText composition_;
+ base::string16 surrounding_text_;
+ base::Optional<gfx::Range> cursor_pos_;
base::i18n::TextDirection direction_ = base::i18n::UNKNOWN_DIRECTION;
DISALLOW_COPY_AND_ASSIGN(TextInput);
diff --git a/components/exo/text_input_unittest.cc b/components/exo/text_input_unittest.cc
index 2aa5e1a6..3dc0ed5 100644
--- a/components/exo/text_input_unittest.cc
+++ b/components/exo/text_input_unittest.cc
@@ -4,6 +4,8 @@
#include "components/exo/text_input.h"
+#include <string>
+
#include "base/strings/utf_string_conversions.h"
#include "components/exo/buffer.h"
#include "components/exo/shell_surface.h"
@@ -117,6 +119,16 @@
return surface_->window()->GetHost()->GetInputMethod();
}
+ void SetCompositionText(const std::string& utf8) {
+ ui::CompositionText t;
+ t.text = base::UTF8ToUTF16(utf8);
+ t.selection = gfx::Range(1u);
+ t.ime_text_spans.push_back(
+ ui::ImeTextSpan(0, t.text.size(), ui::ImeTextSpan::Thickness::kThick));
+ EXPECT_CALL(*delegate(), SetCompositionText(t)).Times(1);
+ text_input()->SetCompositionText(t);
+ }
+
private:
std::unique_ptr<TextInput> text_input_;
@@ -224,31 +236,17 @@
}
TEST_F(TextInputTest, CompositionText) {
- ui::CompositionText t;
- t.text = base::ASCIIToUTF16("composition");
- t.selection = gfx::Range(1u);
- t.ime_text_spans.push_back(
- ui::ImeTextSpan(0, t.text.size(), ui::ImeTextSpan::Thickness::kThick));
+ SetCompositionText("composition");
ui::CompositionText empty;
- EXPECT_CALL(*delegate(), SetCompositionText(t)).Times(1);
EXPECT_CALL(*delegate(), SetCompositionText(empty)).Times(1);
-
- text_input()->SetCompositionText(t);
text_input()->ClearCompositionText();
}
TEST_F(TextInputTest, CommitCompositionText) {
- ui::CompositionText t;
- t.text = base::ASCIIToUTF16("composition");
- t.selection = gfx::Range(1u);
- t.ime_text_spans.push_back(
- ui::ImeTextSpan(0, t.text.size(), ui::ImeTextSpan::Thickness::kThick));
+ SetCompositionText("composition");
- EXPECT_CALL(*delegate(), SetCompositionText(t)).Times(1);
- EXPECT_CALL(*delegate(), Commit(t.text)).Times(1);
-
- text_input()->SetCompositionText(t);
+ EXPECT_CALL(*delegate(), Commit(base::UTF8ToUTF16("composition"))).Times(1);
text_input()->ConfirmCompositionText();
}
@@ -275,5 +273,66 @@
text_input()->InsertChar(ev);
}
+TEST_F(TextInputTest, SurroundingText) {
+ gfx::Range range;
+ EXPECT_FALSE(text_input()->GetTextRange(&range));
+ EXPECT_FALSE(text_input()->GetCompositionTextRange(&range));
+ EXPECT_FALSE(text_input()->GetEditableSelectionRange(&range));
+ base::string16 got_text;
+ EXPECT_FALSE(text_input()->GetTextFromRange(gfx::Range(0, 1), &got_text));
+
+ base::string16 text = base::UTF8ToUTF16("surrounding\xE3\x80\x80text");
+ text_input()->SetSurroundingText(text, 11, 12);
+
+ EXPECT_TRUE(text_input()->GetTextRange(&range));
+ EXPECT_EQ(gfx::Range(0, text.size()).ToString(), range.ToString());
+
+ EXPECT_FALSE(text_input()->GetCompositionTextRange(&range));
+ EXPECT_TRUE(text_input()->GetEditableSelectionRange(&range));
+ EXPECT_EQ(gfx::Range(11, 12).ToString(), range.ToString());
+ EXPECT_TRUE(text_input()->GetTextFromRange(gfx::Range(11, 12), &got_text));
+ EXPECT_EQ(text.substr(11, 1), got_text);
+
+ // DeleteSurroundingText receives the range in UTF8 -- so (11, 14) range is
+ // expected.
+ EXPECT_CALL(*delegate(), DeleteSurroundingText(gfx::Range(11, 14))).Times(1);
+ text_input()->ExtendSelectionAndDelete(0, 0);
+
+ size_t composition_size = std::string("composition").size();
+ SetCompositionText("composition");
+ EXPECT_TRUE(text_input()->GetCompositionTextRange(&range));
+ EXPECT_EQ(gfx::Range(11, 11 + composition_size).ToString(), range.ToString());
+ EXPECT_TRUE(text_input()->GetTextRange(&range));
+ EXPECT_EQ(gfx::Range(0, text.size() - 1 + composition_size).ToString(),
+ range.ToString());
+ EXPECT_TRUE(text_input()->GetEditableSelectionRange(&range));
+ EXPECT_EQ(gfx::Range(11, 12).ToString(), range.ToString());
+}
+
+TEST_F(TextInputTest, GetTextRange) {
+ base::string16 text = base::UTF8ToUTF16("surrounding text");
+ text_input()->SetSurroundingText(text, 11, 12);
+
+ SetCompositionText("composition");
+
+ const struct {
+ gfx::Range range;
+ std::string expected;
+ } kTestCases[] = {
+ {gfx::Range(0, 3), "sur"},
+ {gfx::Range(10, 13), "gco"},
+ {gfx::Range(10, 23), "gcompositiont"},
+ {gfx::Range(12, 15), "omp"},
+ {gfx::Range(12, 23), "ompositiont"},
+ {gfx::Range(22, 25), "tex"},
+ };
+ for (auto& c : kTestCases) {
+ base::string16 result;
+ EXPECT_TRUE(text_input()->GetTextFromRange(c.range, &result))
+ << c.range.ToString();
+ EXPECT_EQ(base::UTF8ToUTF16(c.expected), result) << c.range.ToString();
+ }
+}
+
} // anonymous namespace
} // namespace exo
diff --git a/components/exo/wayland/zwp_text_input_manager.cc b/components/exo/wayland/zwp_text_input_manager.cc
index 15ebfff..73ae15f 100644
--- a/components/exo/wayland/zwp_text_input_manager.cc
+++ b/components/exo/wayland/zwp_text_input_manager.cc
@@ -25,14 +25,6 @@
////////////////////////////////////////////////////////////////////////////////
// text_input_v1 interface:
-size_t OffsetFromUTF8Offset(const base::StringPiece& text, uint32_t offset) {
- return base::UTF8ToUTF16(text.substr(0, offset)).size();
-}
-
-size_t OffsetFromUTF16Offset(const base::StringPiece16& text, uint32_t offset) {
- return base::UTF16ToUTF8(text.substr(0, offset)).size();
-}
-
class WaylandTextInputDelegate : public TextInput::Delegate {
public:
WaylandTextInputDelegate(wl_resource* text_input) : text_input_(text_input) {}
@@ -107,15 +99,13 @@
}
void SetCursor(const gfx::Range& selection) override {
- // TODO(mukai): compute the utf8 offset for |selection| and call
- // zwp_text_input_v1_send_cursor_position.
- NOTIMPLEMENTED();
+ zwp_text_input_v1_send_cursor_position(text_input_, selection.end(),
+ selection.start());
}
void DeleteSurroundingText(const gfx::Range& range) override {
- // TODO(mukai): compute the utf8 offset for |range| and call
- // zwp_text_input_send_delete_surrounding_text.
- NOTIMPLEMENTED();
+ zwp_text_input_v1_send_delete_surrounding_text(text_input_, range.start(),
+ range.length());
}
void SendKey(const ui::KeyEvent& event) override {
@@ -216,7 +206,8 @@
uint32_t anchor) {
TextInput* text_input = GetUserDataAs<TextInput>(resource);
text_input->SetSurroundingText(base::UTF8ToUTF16(text),
- OffsetFromUTF8Offset(text, cursor));
+ OffsetFromUTF8Offset(text, cursor),
+ OffsetFromUTF8Offset(text, anchor));
}
void text_input_set_content_type(wl_client* client,