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 @@
#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 @@ ui::InputMethod* GetInputMethod(aura::Window* window) {
} // 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::Resync() {
}
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 @@ ui::TextInputClient::FocusReason TextInput::GetFocusReason() const {
}
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 @@ bool TextInput::ChangeTextDirectionAndLayoutAlignment(
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) {}
......
......@@ -23,6 +23,9 @@ class KeyboardController;
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 @@ class TextInput : public ui::TextInputClient,
// 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 @@ class TextInput : public ui::TextInputClient,
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 @@ class TextInput : public ui::TextInputClient,
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);
......
......@@ -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 @@ class TextInputTest : public test::ExoTestBase {
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, CaretBounds) {
}
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));
EXPECT_CALL(*delegate(), SetCompositionText(t)).Times(1);
EXPECT_CALL(*delegate(), Commit(t.text)).Times(1);
SetCompositionText("composition");
text_input()->SetCompositionText(t);
EXPECT_CALL(*delegate(), Commit(base::UTF8ToUTF16("composition"))).Times(1);
text_input()->ConfirmCompositionText();
}
......@@ -275,5 +273,66 @@ TEST_F(TextInputTest, InsertCharNormalKey) {
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
......@@ -25,14 +25,6 @@ namespace {
////////////////////////////////////////////////////////////////////////////////
// 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 @@ class WaylandTextInputDelegate : public TextInput::Delegate {
}
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 @@ void text_input_set_surrounding_text(wl_client* client,
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,
......
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