blob: 7f0354fa2269b3dbd30ca89d2e6e1fe9a7cfde40 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/vr/model/text_input_info.h"
#include <algorithm>
#include "base/strings/utf_string_conversions.h"
namespace vr {
namespace {
size_t CommonPrefixLength(const base::string16 a, const base::string16 b) {
size_t a_len = a.length();
size_t b_len = b.length();
size_t i = 0;
while (i < a_len && i < b_len && a[i] == b[i]) {
i++;
}
return i;
}
} // namespace
TextInputInfo::TextInputInfo()
: TextInputInfo(base::UTF8ToUTF16(""),
0,
0,
kDefaultCompositionIndex,
kDefaultCompositionIndex) {}
TextInputInfo::TextInputInfo(base::string16 t)
: TextInputInfo(t,
t.length(),
t.length(),
kDefaultCompositionIndex,
kDefaultCompositionIndex) {}
TextInputInfo::TextInputInfo(base::string16 t,
int sel_start,
int sel_end,
int comp_start,
int comp_end)
: text(t),
selection_start(sel_start),
selection_end(sel_end),
composition_start(comp_start),
composition_end(comp_end) {
ClampIndices();
}
TextInputInfo::TextInputInfo(const TextInputInfo& other)
: text(other.text),
selection_start(other.selection_start),
selection_end(other.selection_end),
composition_start(other.composition_start),
composition_end(other.composition_end) {}
bool TextInputInfo::operator==(const TextInputInfo& other) const {
return text == other.text && selection_start == other.selection_start &&
selection_end == other.selection_end &&
composition_start == other.composition_start &&
composition_end == other.composition_end;
}
bool TextInputInfo::operator!=(const TextInputInfo& other) const {
return !(*this == other);
}
size_t TextInputInfo::SelectionSize() const {
return std::abs(selection_end - selection_start);
}
size_t TextInputInfo::CompositionSize() const {
return composition_end - composition_start;
}
base::string16 TextInputInfo::CommittedTextBeforeCursor() const {
if (composition_start == composition_end)
return text.substr(0, selection_start);
return text.substr(0, composition_start);
}
base::string16 TextInputInfo::ComposingText() const {
if (composition_start == composition_end)
return base::UTF8ToUTF16("");
return text.substr(composition_start, CompositionSize());
}
std::string TextInputInfo::ToString() const {
return base::StringPrintf("t(%s) s(%d, %d) c(%d, %d)",
base::UTF16ToUTF8(text).c_str(), selection_start,
selection_end, composition_start, composition_end);
}
void TextInputInfo::ClampIndices() {
const int len = text.length();
selection_start = std::min(selection_start, len);
selection_end = std::min(selection_end, len);
if (selection_end < selection_start)
selection_end = selection_start;
composition_start = std::min(composition_start, len);
composition_end = std::min(composition_end, len);
if (composition_end <= composition_start) {
composition_start = kDefaultCompositionIndex;
composition_end = kDefaultCompositionIndex;
}
}
EditedText::EditedText() {}
EditedText::EditedText(const EditedText& other)
: current(other.current), previous(other.previous) {}
EditedText::EditedText(const TextInputInfo& new_current,
const TextInputInfo& new_previous)
: current(new_current), previous(new_previous) {}
EditedText::EditedText(base::string16 t) : current(t) {}
bool EditedText::operator==(const EditedText& other) const {
return current == other.current && previous == other.previous;
}
void EditedText::Update(const TextInputInfo& info) {
previous = current;
current = info;
}
std::string EditedText::ToString() const {
return current.ToString() + ", previously " + previous.ToString();
}
TextEdits EditedText::GetDiff() const {
TextEdits edits;
if (current == previous)
return edits;
int common_prefix_length =
CommonPrefixLength(current.CommittedTextBeforeCursor(),
previous.CommittedTextBeforeCursor());
bool had_composition =
previous.CompositionSize() > 0 && current.CompositionSize() == 0;
// If the composition changes while there was a composition previously, we
// first finish the previous composition by clearing then commiting it, then
// we set the new composition.
bool new_composition =
previous.composition_start != current.composition_start &&
previous.CompositionSize() > 0;
if (had_composition || new_composition) {
edits.push_back(TextEditAction(TextEditActionType::CLEAR_COMPOSING_TEXT));
}
int to_delete = 0;
// We only want to delete text if the was no selection previously. In the case
// where there was a selection, its the editor's responsibility to ensure that
// the selected text gets modified when a new edit occurs.
bool had_selection =
previous.SelectionSize() > 0 && current.SelectionSize() == 0;
if (!had_selection) {
to_delete =
previous.CommittedTextBeforeCursor().size() - common_prefix_length;
if (to_delete > 0) {
DCHECK(!had_composition);
edits.push_back(TextEditAction(TextEditActionType::DELETE_TEXT,
base::UTF8ToUTF16(""), -to_delete));
}
}
int to_commit =
current.CommittedTextBeforeCursor().size() - common_prefix_length;
if (to_commit > 0 || had_selection) {
DCHECK(to_delete == 0);
edits.push_back(TextEditAction(TextEditActionType::COMMIT_TEXT,
current.CommittedTextBeforeCursor().substr(
common_prefix_length, to_commit),
to_commit));
}
if (current.CompositionSize() > 0) {
int cursor = current.CompositionSize();
if (!new_composition) {
cursor = current.CompositionSize() - previous.CompositionSize();
}
edits.push_back(TextEditAction(TextEditActionType::SET_COMPOSING_TEXT,
current.ComposingText(), cursor));
}
return edits;
}
static_assert(sizeof(base::string16) + 16 == sizeof(TextInputInfo),
"If new fields are added to TextInputInfo, we must explicitly "
"bump this size and update operator==");
} // namespace vr