| Index: third_party/WebKit/Source/core/editing/commands/InsertIncrementalTextCommand.cpp
|
| diff --git a/third_party/WebKit/Source/core/editing/commands/InsertIncrementalTextCommand.cpp b/third_party/WebKit/Source/core/editing/commands/InsertIncrementalTextCommand.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7bb2e1b91d5eb097233083827bfe15d77d6e7986
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/core/editing/commands/InsertIncrementalTextCommand.cpp
|
| @@ -0,0 +1,178 @@
|
| +// 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 "core/editing/commands/InsertIncrementalTextCommand.h"
|
| +
|
| +#include "core/dom/Document.h"
|
| +#include "core/dom/Element.h"
|
| +#include "core/dom/Text.h"
|
| +#include "core/editing/EditingUtilities.h"
|
| +#include "core/editing/Editor.h"
|
| +#include "core/editing/PlainTextRange.h"
|
| +#include "core/editing/VisibleUnits.h"
|
| +#include "core/editing/iterators/CharacterIterator.h"
|
| +#include "core/html/HTMLSpanElement.h"
|
| +
|
| +namespace blink {
|
| +
|
| +namespace {
|
| +
|
| +size_t computeCommonPrefixLength(const String& str1, const String& str2) {
|
| + const size_t maxCommonPrefixLength = std::min(str1.length(), str2.length());
|
| + for (size_t index = 0; index < maxCommonPrefixLength; ++index) {
|
| + if (str1[index] != str2[index])
|
| + return index;
|
| + }
|
| + return maxCommonPrefixLength;
|
| +}
|
| +
|
| +size_t computeCommonSuffixLength(const String& str1, const String& str2) {
|
| + const size_t length1 = str1.length();
|
| + const size_t length2 = str2.length();
|
| + const size_t maxCommonSuffixLength = std::min(length1, length2);
|
| + for (size_t index = 0; index < maxCommonSuffixLength; ++index) {
|
| + if (str1[length1 - index - 1] != str2[length2 - index - 1])
|
| + return index;
|
| + }
|
| + return maxCommonSuffixLength;
|
| +}
|
| +
|
| +// If current position is at grapheme boundary, return 0; otherwise, return the
|
| +// distance to its nearest left grapheme boundary.
|
| +size_t computeDistanceToLeftGraphemeBoundary(const Position& position) {
|
| + const Position& adjustedPosition = previousPositionOf(
|
| + nextPositionOf(position, PositionMoveType::GraphemeCluster),
|
| + PositionMoveType::GraphemeCluster);
|
| + DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode());
|
| + DCHECK_GE(position.computeOffsetInContainerNode(),
|
| + adjustedPosition.computeOffsetInContainerNode());
|
| + return static_cast<size_t>(position.computeOffsetInContainerNode() -
|
| + adjustedPosition.computeOffsetInContainerNode());
|
| +}
|
| +
|
| +size_t computeCommonGraphemeClusterPrefixLength(
|
| + const String& oldText,
|
| + const String& newText,
|
| + const Element* rootEditableElement) {
|
| + const size_t commonPrefixLength = computeCommonPrefixLength(oldText, newText);
|
| +
|
| + // For grapheme cluster, we should adjust it for grapheme boundary.
|
| + const EphemeralRange& range =
|
| + PlainTextRange(0, commonPrefixLength).createRange(*rootEditableElement);
|
| + if (range.isNull())
|
| + return 0;
|
| + const Position& position = range.endPosition();
|
| + const size_t diff = computeDistanceToLeftGraphemeBoundary(position);
|
| + DCHECK_GE(commonPrefixLength, diff);
|
| + return commonPrefixLength - diff;
|
| +}
|
| +
|
| +// If current position is at grapheme boundary, return 0; otherwise, return the
|
| +// distance to its nearest right grapheme boundary.
|
| +size_t computeDistanceToRightGraphemeBoundary(const Position& position) {
|
| + const Position& adjustedPosition = nextPositionOf(
|
| + previousPositionOf(position, PositionMoveType::GraphemeCluster),
|
| + PositionMoveType::GraphemeCluster);
|
| + DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode());
|
| + DCHECK_GE(adjustedPosition.computeOffsetInContainerNode(),
|
| + position.computeOffsetInContainerNode());
|
| + return static_cast<size_t>(adjustedPosition.computeOffsetInContainerNode() -
|
| + position.computeOffsetInContainerNode());
|
| +}
|
| +
|
| +size_t computeCommonGraphemeClusterSuffixLength(
|
| + const String& oldText,
|
| + const String& newText,
|
| + const Element* rootEditableElement) {
|
| + const size_t commonSuffixLength = computeCommonSuffixLength(oldText, newText);
|
| +
|
| + // For grapheme cluster, we should adjust it for grapheme boundary.
|
| + const EphemeralRange& range =
|
| + PlainTextRange(0, oldText.length() - commonSuffixLength)
|
| + .createRange(*rootEditableElement);
|
| + if (range.isNull())
|
| + return 0;
|
| + const Position& position = range.endPosition();
|
| + const size_t diff = computeDistanceToRightGraphemeBoundary(position);
|
| + DCHECK_GE(commonSuffixLength, diff);
|
| + return commonSuffixLength - diff;
|
| +}
|
| +
|
| +const String computeTextForInsertion(const String& newText,
|
| + const size_t commonPrefixLength,
|
| + const size_t commonSuffixLength) {
|
| + return newText.substring(
|
| + commonPrefixLength,
|
| + newText.length() - commonPrefixLength - commonSuffixLength);
|
| +}
|
| +
|
| +VisibleSelection computeSelectionForInsertion(
|
| + const EphemeralRange& selectionRange,
|
| + const int offset,
|
| + const int length,
|
| + const bool isDirectional) {
|
| + CharacterIterator charIt(selectionRange);
|
| + const EphemeralRange& rangeForInsertion =
|
| + charIt.calculateCharacterSubrange(offset, length);
|
| + const VisibleSelection& selection =
|
| + createVisibleSelection(SelectionInDOMTree::Builder()
|
| + .setBaseAndExtent(rangeForInsertion)
|
| + .setIsDirectional(isDirectional)
|
| + .build());
|
| + return selection;
|
| +}
|
| +
|
| +} // anonymous namespace
|
| +
|
| +InsertIncrementalTextCommand* InsertIncrementalTextCommand::create(
|
| + Document& document,
|
| + const String& text,
|
| + bool selectInsertedText,
|
| + RebalanceType rebalanceType) {
|
| + return new InsertIncrementalTextCommand(document, text, selectInsertedText,
|
| + rebalanceType);
|
| +}
|
| +
|
| +InsertIncrementalTextCommand::InsertIncrementalTextCommand(
|
| + Document& document,
|
| + const String& text,
|
| + bool selectInsertedText,
|
| + RebalanceType rebalanceType)
|
| + : InsertTextCommand(document, text, selectInsertedText, rebalanceType) {}
|
| +
|
| +void InsertIncrementalTextCommand::doApply(EditingState* editingState) {
|
| + const Element* element = endingSelection().rootEditableElement();
|
| + DCHECK(element);
|
| +
|
| + const EphemeralRange selectionRange(endingSelection().start(),
|
| + endingSelection().end());
|
| + const String oldText = plainText(selectionRange);
|
| + const String& newText = m_text;
|
| +
|
| + const size_t newTextLength = newText.length();
|
| + const size_t oldTextLength = oldText.length();
|
| + const size_t commonPrefixLength =
|
| + computeCommonGraphemeClusterPrefixLength(oldText, newText, element);
|
| + // We should ignore common prefix when finding common suffix.
|
| + const size_t commonSuffixLength = computeCommonGraphemeClusterSuffixLength(
|
| + oldText.right(oldTextLength - commonPrefixLength),
|
| + newText.right(newTextLength - commonPrefixLength), element);
|
| + DCHECK_GE(oldTextLength, commonPrefixLength + commonSuffixLength);
|
| +
|
| + m_text =
|
| + computeTextForInsertion(m_text, commonPrefixLength, commonSuffixLength);
|
| +
|
| + const int offset = static_cast<int>(commonPrefixLength);
|
| + const int length =
|
| + static_cast<int>(oldTextLength - commonPrefixLength - commonSuffixLength);
|
| + const VisibleSelection& selectionForInsertion = computeSelectionForInsertion(
|
| + selectionRange, offset, length, endingSelection().isDirectional());
|
| +
|
| + setEndingSelectionWithoutValidation(selectionForInsertion.start(),
|
| + selectionForInsertion.end());
|
| +
|
| + InsertTextCommand::doApply(editingState);
|
| +}
|
| +
|
| +} // namespace blink
|
|
|