text: // 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
  