Commit 2a54625e authored by Jing Wang's avatar Jing Wang Committed by Commit Bot

Implement keep-typing feature for personal info suggester.

keep-typing feature: when the suggestion pops up and the user keeps
typing, if what they type is a prefix of the suggestion, the popup will
not disappear, and in the suggestion text the typed prefix will be
displayed in black and bold. The popup will disappear anyway if the user
has typed more than 10 characters.

Test: tested with Linux emulator
Bug: 1042084
Change-Id: I157cd92cf29bb308e09764109d2eb0333183af7a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2153181
Commit-Queue: Jing Wang <jiwan@chromium.org>
Reviewed-by: default avatarKeith Lee <keithlee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#760375}
parent a5637c5c
...@@ -61,8 +61,6 @@ bool AssistiveSuggester::OnKeyEvent( ...@@ -61,8 +61,6 @@ bool AssistiveSuggester::OnKeyEvent(
if (context_id_ == -1) if (context_id_ == -1)
return false; return false;
// If the user pressed Tab after we show suggestion, we adopt the suggestion,
// otherwise we dismiss it.
// We only track keydown event because the suggesting action is triggered by // We only track keydown event because the suggesting action is triggered by
// surrounding text change, which is triggered by a keydown event. As a // surrounding text change, which is triggered by a keydown event. As a
// result, the next key event after suggesting would be a keyup event of the // result, the next key event after suggesting would be a keyup event of the
...@@ -76,7 +74,7 @@ bool AssistiveSuggester::OnKeyEvent( ...@@ -76,7 +74,7 @@ bool AssistiveSuggester::OnKeyEvent(
return true; return true;
case SuggestionStatus::kDismiss: case SuggestionStatus::kDismiss:
current_suggester_ = nullptr; current_suggester_ = nullptr;
return false; return true;
case SuggestionStatus::kBrowsing: case SuggestionStatus::kBrowsing:
return true; return true;
default: default:
...@@ -108,37 +106,40 @@ bool AssistiveSuggester::OnSurroundingTextChanged(const base::string16& text, ...@@ -108,37 +106,40 @@ bool AssistiveSuggester::OnSurroundingTextChanged(const base::string16& text,
if (context_id_ == -1) if (context_id_ == -1)
return false; return false;
if (IsSuggestionShown()) { if (!Suggest(text, cursor_pos, anchor_pos)) {
DismissSuggestion(); DismissSuggestion();
} }
Suggest(text, cursor_pos, anchor_pos);
return IsSuggestionShown(); return IsSuggestionShown();
} }
void AssistiveSuggester::Suggest(const base::string16& text, bool AssistiveSuggester::Suggest(const base::string16& text,
int cursor_pos, int cursor_pos,
int anchor_pos) { int anchor_pos) {
int len = static_cast<int>(text.length()); int len = static_cast<int>(text.length());
if (cursor_pos > 0 && cursor_pos <= len && if (cursor_pos > 0 && cursor_pos <= len && cursor_pos == anchor_pos &&
base::IsAsciiWhitespace(text[cursor_pos - 1]) && (cursor_pos == len || base::IsAsciiWhitespace(text[cursor_pos])) &&
cursor_pos == anchor_pos && (base::IsAsciiWhitespace(text[cursor_pos - 1]) || IsSuggestionShown())) {
(cursor_pos == len || base::IsAsciiWhitespace(text[cursor_pos]))) {
// |text| could be very long, we get at most |kMaxTextBeforeCursorLength| // |text| could be very long, we get at most |kMaxTextBeforeCursorLength|
// characters before cursor. // characters before cursor.
int start_pos = std::max(0, cursor_pos - kMaxTextBeforeCursorLength); int start_pos = std::max(0, cursor_pos - kMaxTextBeforeCursorLength);
base::string16 text_before_cursor = base::string16 text_before_cursor =
text.substr(start_pos, cursor_pos - start_pos); text.substr(start_pos, cursor_pos - start_pos);
if (IsSuggestionShown()) {
return current_suggester_->Suggest(text_before_cursor);
}
if (IsAssistPersonalInfoEnabled() && if (IsAssistPersonalInfoEnabled() &&
personal_info_suggester_.Suggest(text_before_cursor)) { personal_info_suggester_.Suggest(text_before_cursor)) {
current_suggester_ = &personal_info_suggester_; current_suggester_ = &personal_info_suggester_;
return true;
} else if (IsEmojiSuggestAdditionEnabled() && } else if (IsEmojiSuggestAdditionEnabled() &&
emoji_suggester_.Suggest(text_before_cursor)) { emoji_suggester_.Suggest(text_before_cursor)) {
current_suggester_ = &emoji_suggester_; current_suggester_ = &emoji_suggester_;
RecordAssistiveCoverage(current_suggester_->GetProposeActionType()); RecordAssistiveCoverage(current_suggester_->GetProposeActionType());
} else { return true;
current_suggester_ = nullptr;
} }
} }
return false;
} }
void AssistiveSuggester::DismissSuggestion() { void AssistiveSuggester::DismissSuggestion() {
......
...@@ -50,9 +50,9 @@ class AssistiveSuggester { ...@@ -50,9 +50,9 @@ class AssistiveSuggester {
const ::input_method::InputMethodEngineBase::KeyboardEvent& event); const ::input_method::InputMethodEngineBase::KeyboardEvent& event);
private: private:
// Check if any suggestion text should be displayed according to the // Returns if any suggestion text should be displayed according to the
// surrounding text information. // surrounding text information.
void Suggest(const base::string16& text, int cursor_pos, int anchor_pos); bool Suggest(const base::string16& text, int cursor_pos, int anchor_pos);
void DismissSuggestion(); void DismissSuggestion();
......
...@@ -78,6 +78,7 @@ void AssistiveWindowControllerImpl::ShowSuggestion( ...@@ -78,6 +78,7 @@ void AssistiveWindowControllerImpl::ShowSuggestion(
if (!suggestion_window_view_) if (!suggestion_window_view_)
Init(); Init();
suggestion_text_ = text; suggestion_text_ = text;
confirmed_length_ = confirmed_length;
suggestion_window_view_->Show(text, confirmed_length, show_tab); suggestion_window_view_->Show(text, confirmed_length, show_tab);
} }
...@@ -85,5 +86,9 @@ base::string16 AssistiveWindowControllerImpl::GetSuggestionText() const { ...@@ -85,5 +86,9 @@ base::string16 AssistiveWindowControllerImpl::GetSuggestionText() const {
return suggestion_text_; return suggestion_text_;
} }
size_t AssistiveWindowControllerImpl::GetConfirmedLength() const {
return confirmed_length_;
}
} // namespace input_method } // namespace input_method
} // namespace chromeos } // namespace chromeos
...@@ -37,6 +37,7 @@ class AssistiveWindowControllerImpl ...@@ -37,6 +37,7 @@ class AssistiveWindowControllerImpl
const bool show_tab) override; const bool show_tab) override;
void HideSuggestion() override; void HideSuggestion() override;
base::string16 GetSuggestionText() const override; base::string16 GetSuggestionText() const override;
size_t GetConfirmedLength() const override;
void FocusStateChanged() override; void FocusStateChanged() override;
void OnWidgetClosing(views::Widget* widget) override; void OnWidgetClosing(views::Widget* widget) override;
...@@ -44,6 +45,7 @@ class AssistiveWindowControllerImpl ...@@ -44,6 +45,7 @@ class AssistiveWindowControllerImpl
ui::ime::SuggestionWindowView* suggestion_window_view_ = nullptr; ui::ime::SuggestionWindowView* suggestion_window_view_ = nullptr;
base::string16 suggestion_text_; base::string16 suggestion_text_;
size_t confirmed_length_;
DISALLOW_COPY_AND_ASSIGN(AssistiveWindowControllerImpl); DISALLOW_COPY_AND_ASSIGN(AssistiveWindowControllerImpl);
}; };
......
...@@ -127,7 +127,7 @@ SuggestionStatus EmojiSuggester::HandleKeyEvent( ...@@ -127,7 +127,7 @@ SuggestionStatus EmojiSuggester::HandleKeyEvent(
: candidate_id_ = static_cast<int>(candidates_.size()) - 1; : candidate_id_ = static_cast<int>(candidates_.size()) - 1;
engine_->SetCursorPosition(context_id_, candidate_id_, &error); engine_->SetCursorPosition(context_id_, candidate_id_, &error);
status = SuggestionStatus::kBrowsing; status = SuggestionStatus::kBrowsing;
} else { } else if (event.key == "Esc") {
DismissSuggestion(); DismissSuggestion();
suggestion_shown_ = false; suggestion_shown_ = false;
status = SuggestionStatus::kDismiss; status = SuggestionStatus::kDismiss;
...@@ -139,18 +139,19 @@ SuggestionStatus EmojiSuggester::HandleKeyEvent( ...@@ -139,18 +139,19 @@ SuggestionStatus EmojiSuggester::HandleKeyEvent(
} }
bool EmojiSuggester::Suggest(const base::string16& text) { bool EmojiSuggester::Suggest(const base::string16& text) {
if (emoji_map_.empty()) if (emoji_map_.empty() || !base::IsAsciiWhitespace(text[text.length() - 1]))
return false; return false;
std::string last_word = GetLastWord(base::UTF16ToUTF8(text)); std::string last_word = GetLastWord(base::UTF16ToUTF8(text));
if (!last_word.empty() && emoji_map_.count(last_word)) { if (!last_word.empty() && emoji_map_.count(last_word)) {
ShowSuggestion(last_word); ShowSuggestion(last_word);
suggestion_shown_ = true; return true;
} }
return suggestion_shown_; return false;
} }
void EmojiSuggester::ShowSuggestion(const std::string& text) { void EmojiSuggester::ShowSuggestion(const std::string& text) {
std::string error; std::string error;
suggestion_shown_ = true;
candidates_.clear(); candidates_.clear();
const std::vector<std::string>& candidates = emoji_map_.at(text); const std::vector<std::string>& candidates = emoji_map_.at(text);
for (size_t i = 0; i < candidates.size(); i++) { for (size_t i = 0; i < candidates.size(); i++) {
...@@ -173,6 +174,7 @@ void EmojiSuggester::ShowSuggestion(const std::string& text) { ...@@ -173,6 +174,7 @@ void EmojiSuggester::ShowSuggestion(const std::string& text) {
void EmojiSuggester::DismissSuggestion() { void EmojiSuggester::DismissSuggestion() {
std::string error; std::string error;
suggestion_shown_ = false;
engine_->SetCandidateWindowVisible(false, &error); engine_->SetCandidateWindowVisible(false, &error);
if (!error.empty()) { if (!error.empty()) {
LOG(ERROR) << "Failed to dismiss suggestion. " << error; LOG(ERROR) << "Failed to dismiss suggestion. " << error;
......
...@@ -270,6 +270,11 @@ bool InputMethodEngine::AcceptSuggestion(int context_id, std::string* error) { ...@@ -270,6 +270,11 @@ bool InputMethodEngine::AcceptSuggestion(int context_id, std::string* error) {
return false; return false;
} }
FinishComposingText(context_id_, error);
if (!error->empty()) {
return false;
}
IMEAssistiveWindowHandlerInterface* aw_handler = IMEAssistiveWindowHandlerInterface* aw_handler =
ui::IMEBridge::Get()->GetAssistiveWindowHandler(); ui::IMEBridge::Get()->GetAssistiveWindowHandler();
if (aw_handler) { if (aw_handler) {
...@@ -278,6 +283,11 @@ bool InputMethodEngine::AcceptSuggestion(int context_id, std::string* error) { ...@@ -278,6 +283,11 @@ bool InputMethodEngine::AcceptSuggestion(int context_id, std::string* error) {
*error = kSuggestionNotFound; *error = kSuggestionNotFound;
return false; return false;
} }
size_t confirmed_length = aw_handler->GetConfirmedLength();
if (confirmed_length > 0) {
DeleteSurroundingText(context_id_, -confirmed_length, confirmed_length,
error);
}
CommitText(context_id_, (base::UTF16ToUTF8(suggestion_text)).c_str(), CommitText(context_id_, (base::UTF16ToUTF8(suggestion_text)).c_str(),
error); error);
aw_handler->HideSuggestion(); aw_handler->HideSuggestion();
......
...@@ -16,6 +16,7 @@ namespace chromeos { ...@@ -16,6 +16,7 @@ namespace chromeos {
namespace { namespace {
const size_t kMaxConfirmedTextLength = 10;
const char kAssistEmailPrefix[] = "my email is "; const char kAssistEmailPrefix[] = "my email is ";
const char kAssistNamePrefix[] = "my name is "; const char kAssistNamePrefix[] = "my name is ";
const char kAssistAddressPrefix[] = "my address is "; const char kAssistAddressPrefix[] = "my address is ";
...@@ -64,62 +65,86 @@ void PersonalInfoSuggester::OnBlur() { ...@@ -64,62 +65,86 @@ void PersonalInfoSuggester::OnBlur() {
SuggestionStatus PersonalInfoSuggester::HandleKeyEvent( SuggestionStatus PersonalInfoSuggester::HandleKeyEvent(
const InputMethodEngineBase::KeyboardEvent& event) { const InputMethodEngineBase::KeyboardEvent& event) {
if (suggestion_shown_) { if (suggestion_shown_) {
suggestion_shown_ = false;
if (event.key == "Tab" || event.key == "Right") { if (event.key == "Tab" || event.key == "Right") {
AcceptSuggestion(); AcceptSuggestion();
return SuggestionStatus::kAccept; return SuggestionStatus::kAccept;
} else if (event.key == "Esc") {
DismissSuggestion();
return SuggestionStatus::kDismiss;
} }
DismissSuggestion();
return SuggestionStatus::kDismiss;
} }
return SuggestionStatus::kNotHandled; return SuggestionStatus::kNotHandled;
} }
bool PersonalInfoSuggester::Suggest(const base::string16& text) { bool PersonalInfoSuggester::Suggest(const base::string16& text) {
if (suggestion_shown_) {
size_t text_length = text.length();
bool matched = false;
for (size_t offset = 0;
offset < suggestion_.length() && offset < text_length &&
offset < kMaxConfirmedTextLength;
offset++) {
base::string16 text_before = text.substr(0, text_length - offset);
base::string16 confirmed_text = text.substr(text_length - offset);
if (base::StartsWith(suggestion_, confirmed_text,
base::CompareCase::INSENSITIVE_ASCII) &&
suggestion_ == GetSuggestion(text_before)) {
matched = true;
ShowSuggestion(suggestion_, offset);
break;
}
}
return matched;
} else {
suggestion_ = GetSuggestion(text);
if (!suggestion_.empty())
ShowSuggestion(suggestion_, 0);
return suggestion_shown_;
}
}
base::string16 PersonalInfoSuggester::GetSuggestion(
const base::string16& text) {
proposed_action_type_ = ProposeAssistiveAction(text); proposed_action_type_ = ProposeAssistiveAction(text);
if (proposed_action_type_ == AssistiveType::kGenericAction) if (proposed_action_type_ == AssistiveType::kGenericAction)
return false; return base::EmptyString16();
if (proposed_action_type_ == AssistiveType::kPersonalEmail)
return base::UTF8ToUTF16(profile_->GetProfileUserName());
auto autofill_profiles = personal_data_manager_->GetProfilesToSuggest();
if (autofill_profiles.empty())
return base::EmptyString16();
// Currently, we are just picking the first candidate, will improve the
// strategy in the future.
auto* profile = autofill_profiles[0];
base::string16 suggestion; base::string16 suggestion;
if (proposed_action_type_ == AssistiveType::kPersonalEmail) { switch (proposed_action_type_) {
suggestion = base::UTF8ToUTF16(profile_->GetProfileUserName()); case AssistiveType::kPersonalName:
} else { suggestion = profile->GetRawInfo(autofill::ServerFieldType::NAME_FULL);
auto autofill_profiles = personal_data_manager_->GetProfilesToSuggest(); break;
if (autofill_profiles.empty()) case AssistiveType::kPersonalAddress:
return false; suggestion = profile->GetRawInfo(
autofill::ServerFieldType::ADDRESS_HOME_STREET_ADDRESS);
// Currently, we are just picking the first candidate, will improve the break;
// strategy in the future. case AssistiveType::kPersonalPhoneNumber:
auto* profile = autofill_profiles[0]; suggestion = profile->GetRawInfo(
switch (proposed_action_type_) { autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER);
case AssistiveType::kPersonalName: break;
suggestion = profile->GetRawInfo(autofill::ServerFieldType::NAME_FULL); default:
break; NOTREACHED();
case AssistiveType::kPersonalAddress: break;
suggestion = profile->GetRawInfo(
autofill::ServerFieldType::ADDRESS_HOME_STREET_ADDRESS);
break;
case AssistiveType::kPersonalPhoneNumber:
suggestion = profile->GetRawInfo(
autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER);
break;
default:
NOTREACHED();
break;
}
} }
return suggestion;
if (suggestion.empty())
return false;
ShowSuggestion(suggestion);
suggestion_shown_ = true;
return true;
} }
void PersonalInfoSuggester::ShowSuggestion(const base::string16& text) { void PersonalInfoSuggester::ShowSuggestion(const base::string16& text,
const size_t confirmed_length) {
std::string error; std::string error;
engine_->SetSuggestion(context_id_, text, 0, true, &error); suggestion_shown_ = true;
engine_->SetSuggestion(context_id_, text, confirmed_length, true, &error);
if (!error.empty()) { if (!error.empty()) {
LOG(ERROR) << "Fail to show suggestion. " << error; LOG(ERROR) << "Fail to show suggestion. " << error;
} }
...@@ -131,6 +156,7 @@ AssistiveType PersonalInfoSuggester::GetProposeActionType() { ...@@ -131,6 +156,7 @@ AssistiveType PersonalInfoSuggester::GetProposeActionType() {
void PersonalInfoSuggester::AcceptSuggestion() { void PersonalInfoSuggester::AcceptSuggestion() {
std::string error; std::string error;
suggestion_shown_ = false;
engine_->AcceptSuggestion(context_id_, &error); engine_->AcceptSuggestion(context_id_, &error);
if (!error.empty()) { if (!error.empty()) {
LOG(ERROR) << "Failed to accept suggestion. " << error; LOG(ERROR) << "Failed to accept suggestion. " << error;
...@@ -139,6 +165,7 @@ void PersonalInfoSuggester::AcceptSuggestion() { ...@@ -139,6 +165,7 @@ void PersonalInfoSuggester::AcceptSuggestion() {
void PersonalInfoSuggester::DismissSuggestion() { void PersonalInfoSuggester::DismissSuggestion() {
std::string error; std::string error;
suggestion_shown_ = false;
engine_->DismissSuggestion(context_id_, &error); engine_->DismissSuggestion(context_id_, &error);
if (!error.empty()) { if (!error.empty()) {
LOG(ERROR) << "Failed to dismiss suggestion. " << error; LOG(ERROR) << "Failed to dismiss suggestion. " << error;
......
...@@ -40,11 +40,11 @@ class PersonalInfoSuggester : public Suggester { ...@@ -40,11 +40,11 @@ class PersonalInfoSuggester : public Suggester {
AssistiveType GetProposeActionType() override; AssistiveType GetProposeActionType() override;
private: private:
// Get the suggestion according to |text_before_cursor|. // Get the suggestion according to |text|.
base::string16 GetPersonalInfoSuggestion( base::string16 GetSuggestion(const base::string16& text);
const base::string16& text_before_cursor);
void ShowSuggestion(const base::string16& text); void ShowSuggestion(const base::string16& text,
const size_t confirmed_length);
void AcceptSuggestion(); void AcceptSuggestion();
...@@ -64,6 +64,9 @@ class PersonalInfoSuggester : public Suggester { ...@@ -64,6 +64,9 @@ class PersonalInfoSuggester : public Suggester {
// If we are showing a suggestion right now. // If we are showing a suggestion right now.
bool suggestion_shown_ = false; bool suggestion_shown_ = false;
// The current suggestion text shown.
base::string16 suggestion_;
}; };
} // namespace chromeos } // namespace chromeos
......
...@@ -30,6 +30,9 @@ class COMPONENT_EXPORT(UI_BASE_IME) IMEAssistiveWindowHandlerInterface { ...@@ -30,6 +30,9 @@ class COMPONENT_EXPORT(UI_BASE_IME) IMEAssistiveWindowHandlerInterface {
// Called to get the current suggestion text. // Called to get the current suggestion text.
virtual base::string16 GetSuggestionText() const = 0; virtual base::string16 GetSuggestionText() const = 0;
// Called to get length of the confirmed part of suggestion text.
virtual size_t GetConfirmedLength() const = 0;
// Called when the application changes its caret bounds. // Called when the application changes its caret bounds.
virtual void SetBounds(const gfx::Rect& cursor_bounds) = 0; virtual void SetBounds(const gfx::Rect& cursor_bounds) = 0;
......
...@@ -70,7 +70,9 @@ void SuggestionView::SetView(const base::string16& text, ...@@ -70,7 +70,9 @@ void SuggestionView::SetView(const base::string16& text,
void SuggestionView::SetSuggestionText(const base::string16& text, void SuggestionView::SetSuggestionText(const base::string16& text,
const size_t confirmed_length) { const size_t confirmed_length) {
// SetText clears the existing style. // SetText clears the existing style only if the text to set is different from
// the previous one.
suggestion_label_->SetText(base::EmptyString16());
suggestion_label_->SetText(text); suggestion_label_->SetText(text);
if (confirmed_length != 0) { if (confirmed_length != 0) {
views::StyledLabel::RangeStyleInfo confirmed_style; views::StyledLabel::RangeStyleInfo confirmed_style;
......
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