blob: be0e603a8351800d5db917d2c77d72339c52440c [file] [log] [blame]
// Copyright (c) 2016 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 "third_party/blink/renderer/core/editing/commands/insert_incremental_text_command.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/iterators/character_iterator.h"
#include "third_party/blink/renderer/core/editing/plain_text_range.h"
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include "third_party/blink/renderer/core/editing/state_machines/forward_code_point_state_machine.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/html/html_span_element.h"
namespace blink {
namespace {
wtf_size_t ComputeCommonPrefixLength(const String& str1, const String& str2) {
const wtf_size_t max_common_prefix_length =
std::min(str1.length(), str2.length());
ForwardCodePointStateMachine code_point_state_machine;
wtf_size_t result = 0;
for (wtf_size_t index = 0; index < max_common_prefix_length; ++index) {
if (str1[index] != str2[index])
return result;
code_point_state_machine.FeedFollowingCodeUnit(str1[index]);
if (!code_point_state_machine.AtCodePointBoundary())
continue;
result = index;
}
return max_common_prefix_length;
}
wtf_size_t ComputeCommonSuffixLength(const String& str1, const String& str2) {
const wtf_size_t length1 = str1.length();
const wtf_size_t length2 = str2.length();
const wtf_size_t max_common_suffix_length = std::min(length1, length2);
for (wtf_size_t index = 0; index < max_common_suffix_length; ++index) {
if (str1[length1 - index - 1] != str2[length2 - index - 1])
return index;
}
return max_common_suffix_length;
}
wtf_size_t ComputeCommonGraphemeClusterPrefixLength(
const Position& selection_start,
const String& old_text,
const String& new_text) {
const wtf_size_t common_prefix_length =
ComputeCommonPrefixLength(old_text, new_text);
const int selection_offset = selection_start.ComputeOffsetInContainerNode();
const ContainerNode* selection_node =
selection_start.ComputeContainerNode()->parentNode();
// For grapheme cluster, we should adjust it for grapheme boundary.
const EphemeralRange& range =
PlainTextRange(0, selection_offset + common_prefix_length)
.CreateRange(*selection_node);
if (range.IsNull())
return 0;
const Position& position = range.EndPosition();
const wtf_size_t diff = ComputeDistanceToLeftGraphemeBoundary(position);
DCHECK_GE(common_prefix_length, diff);
return common_prefix_length - diff;
}
wtf_size_t ComputeCommonGraphemeClusterSuffixLength(
const Position& selection_start,
const String& old_text,
const String& new_text) {
const wtf_size_t common_suffix_length =
ComputeCommonSuffixLength(old_text, new_text);
const int selection_offset = selection_start.ComputeOffsetInContainerNode();
const ContainerNode* selection_node =
selection_start.ComputeContainerNode()->parentNode();
// For grapheme cluster, we should adjust it for grapheme boundary.
const EphemeralRange& range =
PlainTextRange(
0, selection_offset + old_text.length() - common_suffix_length)
.CreateRange(*selection_node);
if (range.IsNull())
return 0;
const Position& position = range.EndPosition();
const wtf_size_t diff = ComputeDistanceToRightGraphemeBoundary(position);
if (diff > common_suffix_length)
return 0;
return common_suffix_length - diff;
}
const String ComputeTextForInsertion(const String& new_text,
const wtf_size_t common_prefix_length,
const wtf_size_t common_suffix_length) {
return new_text.Substring(
common_prefix_length,
new_text.length() - common_prefix_length - common_suffix_length);
}
SelectionInDOMTree ComputeSelectionForInsertion(
const EphemeralRange& selection_range,
const int offset,
const int length) {
CharacterIterator char_it(
selection_range,
TextIteratorBehavior::EmitsObjectReplacementCharacterBehavior());
const EphemeralRange& range_for_insertion =
char_it.CalculateCharacterSubrange(offset, length);
return SelectionInDOMTree::Builder()
.SetBaseAndExtent(range_for_insertion)
.Build();
}
} // anonymous namespace
InsertIncrementalTextCommand::InsertIncrementalTextCommand(
Document& document,
const String& text,
RebalanceType rebalance_type)
: InsertTextCommand(document, text, rebalance_type) {}
void InsertIncrementalTextCommand::DoApply(EditingState* editing_state) {
DCHECK(!GetDocument().NeedsLayoutTreeUpdate());
const Element* element = RootEditableElementOf(EndingSelection().Base());
DCHECK(element);
const VisibleSelection& visible_selection = EndingVisibleSelection();
const EphemeralRange selection_range(visible_selection.Start(),
visible_selection.End());
const String old_text = PlainText(
selection_range,
TextIteratorBehavior::EmitsObjectReplacementCharacterBehavior());
const String& new_text = text_;
const Position& selection_start = visible_selection.Start();
const wtf_size_t new_text_length = new_text.length();
const wtf_size_t old_text_length = old_text.length();
const wtf_size_t common_prefix_length =
ComputeCommonGraphemeClusterPrefixLength(selection_start, old_text,
new_text);
// We should ignore common prefix when finding common suffix.
const wtf_size_t common_suffix_length =
ComputeCommonGraphemeClusterSuffixLength(
selection_start,
old_text.Right(old_text_length - common_prefix_length),
new_text.Right(new_text_length - common_prefix_length));
DCHECK_GE(old_text_length, common_prefix_length + common_suffix_length);
text_ = ComputeTextForInsertion(text_, common_prefix_length,
common_suffix_length);
const int offset = static_cast<int>(common_prefix_length);
const int length = static_cast<int>(old_text_length - common_prefix_length -
common_suffix_length);
const VisibleSelection& selection_for_insertion = CreateVisibleSelection(
ComputeSelectionForInsertion(selection_range, offset, length));
SetEndingSelectionWithoutValidation(selection_for_insertion.Start(),
selection_for_insertion.End());
InsertTextCommand::DoApply(editing_state);
}
} // namespace blink