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