Commit cf1f036f authored by Jun Mukai's avatar Jun Mukai Committed by Commit Bot

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: default avatarMitsuru Oshima <oshima@chromium.org>
Reviewed-by: default avatarYuichiro Hanada <yhanada@chromium.org>
Cr-Commit-Position: refs/heads/master@{#629807}
parent cf8d586a
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
#include "components/exo/text_input.h" #include "components/exo/text_input.h"
#include <algorithm>
#include "base/strings/utf_string_conversions.h"
#include "components/exo/surface.h" #include "components/exo/surface.h"
#include "components/exo/wm_helper.h" #include "components/exo/wm_helper.h"
#include "third_party/icu/source/common/unicode/uchar.h" #include "third_party/icu/source/common/unicode/uchar.h"
...@@ -25,6 +28,14 @@ ui::InputMethod* GetInputMethod(aura::Window* window) { ...@@ -25,6 +28,14 @@ ui::InputMethod* GetInputMethod(aura::Window* window) {
} // namespace } // 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) TextInput::TextInput(std::unique_ptr<Delegate> delegate)
: delegate_(std::move(delegate)) {} : delegate_(std::move(delegate)) {}
...@@ -69,8 +80,14 @@ void TextInput::Resync() { ...@@ -69,8 +80,14 @@ void TextInput::Resync() {
} }
void TextInput::SetSurroundingText(const base::string16& text, void TextInput::SetSurroundingText(const base::string16& text,
uint32_t cursor_pos) { uint32_t cursor_pos,
NOTIMPLEMENTED(); 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, void TextInput::SetTypeModeFlags(ui::TextInputType type,
...@@ -163,37 +180,87 @@ ui::TextInputClient::FocusReason TextInput::GetFocusReason() const { ...@@ -163,37 +180,87 @@ ui::TextInputClient::FocusReason TextInput::GetFocusReason() const {
} }
bool TextInput::GetTextRange(gfx::Range* range) const { bool TextInput::GetTextRange(gfx::Range* range) const {
NOTIMPLEMENTED_LOG_ONCE(); if (!cursor_pos_)
return false; 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 { bool TextInput::GetCompositionTextRange(gfx::Range* range) const {
NOTIMPLEMENTED_LOG_ONCE(); if (!cursor_pos_ || composition_.text.empty())
return false; 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 { bool TextInput::GetEditableSelectionRange(gfx::Range* range) const {
NOTIMPLEMENTED_LOG_ONCE(); if (!cursor_pos_)
return false; return false;
range->set_start(cursor_pos_->start());
range->set_end(cursor_pos_->end());
return true;
} }
bool TextInput::SetEditableSelectionRange(const gfx::Range& range) { bool TextInput::SetEditableSelectionRange(const gfx::Range& range) {
NOTIMPLEMENTED_LOG_ONCE(); if (surrounding_text_.size() < range.GetMax())
return false; 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) { bool TextInput::DeleteRange(const gfx::Range& range) {
// TODO(mukai): call delegate_->DeleteSurroundingText(range) once it's if (surrounding_text_.size() < range.GetMax())
// supported.
NOTIMPLEMENTED_LOG_ONCE();
return false; 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, bool TextInput::GetTextFromRange(const gfx::Range& range,
base::string16* text) const { base::string16* text) const {
// TODO(mukai): support of surrounding text. gfx::Range text_range;
NOTIMPLEMENTED_LOG_ONCE(); if (!GetTextRange(&text_range) || !text_range.Contains(range))
return false; 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() { void TextInput::OnInputMethodChanged() {
...@@ -214,7 +281,17 @@ bool TextInput::ChangeTextDirectionAndLayoutAlignment( ...@@ -214,7 +281,17 @@ bool TextInput::ChangeTextDirectionAndLayoutAlignment(
return true; 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) {} void TextInput::EnsureCaretNotInRect(const gfx::Rect& rect) {}
......
...@@ -23,6 +23,9 @@ class KeyboardController; ...@@ -23,6 +23,9 @@ class KeyboardController;
namespace exo { namespace exo {
class Surface; 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. // This class bridges the ChromeOS input method and a text-input context.
class TextInput : public ui::TextInputClient, class TextInput : public ui::TextInputClient,
public keyboard::KeyboardControllerObserver { public keyboard::KeyboardControllerObserver {
...@@ -47,10 +50,11 @@ class TextInput : public ui::TextInputClient, ...@@ -47,10 +50,11 @@ class TextInput : public ui::TextInputClient,
// Commit |text| to the current text input session. // Commit |text| to the current text input session.
virtual void Commit(const base::string16& text) = 0; 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; 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; virtual void DeleteSurroundingText(const gfx::Range& range) = 0;
// Sends a key event. // Sends a key event.
...@@ -83,7 +87,9 @@ class TextInput : public ui::TextInputClient, ...@@ -83,7 +87,9 @@ class TextInput : public ui::TextInputClient,
void Resync(); void Resync();
// Sets the surrounding text in the app. // 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|. // Sets the text input type, mode, flags, and |should_do_learning|.
void SetTypeModeFlags(ui::TextInputType type, void SetTypeModeFlags(ui::TextInputType type,
...@@ -148,6 +154,8 @@ class TextInput : public ui::TextInputClient, ...@@ -148,6 +154,8 @@ class TextInput : public ui::TextInputClient,
int flags_ = ui::TEXT_INPUT_FLAG_NONE; int flags_ = ui::TEXT_INPUT_FLAG_NONE;
bool should_do_learning_ = true; bool should_do_learning_ = true;
ui::CompositionText composition_; ui::CompositionText composition_;
base::string16 surrounding_text_;
base::Optional<gfx::Range> cursor_pos_;
base::i18n::TextDirection direction_ = base::i18n::UNKNOWN_DIRECTION; base::i18n::TextDirection direction_ = base::i18n::UNKNOWN_DIRECTION;
DISALLOW_COPY_AND_ASSIGN(TextInput); DISALLOW_COPY_AND_ASSIGN(TextInput);
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "components/exo/text_input.h" #include "components/exo/text_input.h"
#include <string>
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "components/exo/buffer.h" #include "components/exo/buffer.h"
#include "components/exo/shell_surface.h" #include "components/exo/shell_surface.h"
...@@ -117,6 +119,16 @@ class TextInputTest : public test::ExoTestBase { ...@@ -117,6 +119,16 @@ class TextInputTest : public test::ExoTestBase {
return surface_->window()->GetHost()->GetInputMethod(); 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: private:
std::unique_ptr<TextInput> text_input_; std::unique_ptr<TextInput> text_input_;
...@@ -224,31 +236,17 @@ TEST_F(TextInputTest, CaretBounds) { ...@@ -224,31 +236,17 @@ TEST_F(TextInputTest, CaretBounds) {
} }
TEST_F(TextInputTest, CompositionText) { TEST_F(TextInputTest, CompositionText) {
ui::CompositionText t; SetCompositionText("composition");
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));
ui::CompositionText empty; ui::CompositionText empty;
EXPECT_CALL(*delegate(), SetCompositionText(t)).Times(1);
EXPECT_CALL(*delegate(), SetCompositionText(empty)).Times(1); EXPECT_CALL(*delegate(), SetCompositionText(empty)).Times(1);
text_input()->SetCompositionText(t);
text_input()->ClearCompositionText(); text_input()->ClearCompositionText();
} }
TEST_F(TextInputTest, CommitCompositionText) { TEST_F(TextInputTest, CommitCompositionText) {
ui::CompositionText t; SetCompositionText("composition");
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));
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(); text_input()->ConfirmCompositionText();
} }
...@@ -275,5 +273,66 @@ TEST_F(TextInputTest, InsertCharNormalKey) { ...@@ -275,5 +273,66 @@ TEST_F(TextInputTest, InsertCharNormalKey) {
text_input()->InsertChar(ev); 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 } // anonymous namespace
} // namespace exo } // namespace exo
...@@ -25,14 +25,6 @@ namespace { ...@@ -25,14 +25,6 @@ namespace {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// text_input_v1 interface: // 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 { class WaylandTextInputDelegate : public TextInput::Delegate {
public: public:
WaylandTextInputDelegate(wl_resource* text_input) : text_input_(text_input) {} WaylandTextInputDelegate(wl_resource* text_input) : text_input_(text_input) {}
...@@ -107,15 +99,13 @@ class WaylandTextInputDelegate : public TextInput::Delegate { ...@@ -107,15 +99,13 @@ class WaylandTextInputDelegate : public TextInput::Delegate {
} }
void SetCursor(const gfx::Range& selection) override { void SetCursor(const gfx::Range& selection) override {
// TODO(mukai): compute the utf8 offset for |selection| and call zwp_text_input_v1_send_cursor_position(text_input_, selection.end(),
// zwp_text_input_v1_send_cursor_position. selection.start());
NOTIMPLEMENTED();
} }
void DeleteSurroundingText(const gfx::Range& range) override { void DeleteSurroundingText(const gfx::Range& range) override {
// TODO(mukai): compute the utf8 offset for |range| and call zwp_text_input_v1_send_delete_surrounding_text(text_input_, range.start(),
// zwp_text_input_send_delete_surrounding_text. range.length());
NOTIMPLEMENTED();
} }
void SendKey(const ui::KeyEvent& event) override { void SendKey(const ui::KeyEvent& event) override {
...@@ -216,7 +206,8 @@ void text_input_set_surrounding_text(wl_client* client, ...@@ -216,7 +206,8 @@ void text_input_set_surrounding_text(wl_client* client,
uint32_t anchor) { uint32_t anchor) {
TextInput* text_input = GetUserDataAs<TextInput>(resource); TextInput* text_input = GetUserDataAs<TextInput>(resource);
text_input->SetSurroundingText(base::UTF8ToUTF16(text), text_input->SetSurroundingText(base::UTF8ToUTF16(text),
OffsetFromUTF8Offset(text, cursor)); OffsetFromUTF8Offset(text, cursor),
OffsetFromUTF8Offset(text, anchor));
} }
void text_input_set_content_type(wl_client* client, void text_input_set_content_type(wl_client* client,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment