Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(351)

Unified Diff: third_party/WebKit/Source/core/editing/SelectionModifierWord.cpp

Issue 2908083002: Move LeftWordPosition/RightWordPosition() to SelectionModifierWord.cpp (Closed)
Patch Set: 2017-05-29T16:51:51 Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/core/editing/SelectionModifierWord.cpp
diff --git a/third_party/WebKit/Source/core/editing/SelectionModifierWord.cpp b/third_party/WebKit/Source/core/editing/SelectionModifierWord.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e60408da1721eca99bae46a6957e4998936b1d13
--- /dev/null
+++ b/third_party/WebKit/Source/core/editing/SelectionModifierWord.cpp
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "core/editing/EditingUtilities.h"
+#include "core/editing/RenderedPosition.h"
+#include "core/editing/VisibleUnits.h"
+#include "core/layout/line/InlineTextBox.h"
+#include "core/layout/line/RootInlineBox.h"
+#include "platform/text/TextBreakIterator.h"
+
+namespace blink {
+
+namespace {
+
+// This class holds a list of |InlineBox| in logical order.
+// TDOO(editing-dev): We should utilize |CachedLogicallyOrderedLeafBoxes| class
+// in |CompositeEditCommand::DeleteInsignificantText()|.
+class CachedLogicallyOrderedLeafBoxes final {
+ public:
+ CachedLogicallyOrderedLeafBoxes() = default;
+
+ const InlineTextBox* PreviousTextBox(const RootInlineBox*,
+ const InlineTextBox*);
+ const InlineTextBox* NextTextBox(const RootInlineBox*, const InlineTextBox*);
+
+ size_t size() const { return leaf_boxes_.size(); }
+ const InlineBox* FirstBox() const { return leaf_boxes_[0]; }
+
+ private:
+ const Vector<InlineBox*>& CollectBoxes(const RootInlineBox*);
+ int BoxIndexInLeaves(const InlineTextBox*) const;
+
+ const RootInlineBox* root_inline_box_ = nullptr;
+ Vector<InlineBox*> leaf_boxes_;
+};
+
+const InlineTextBox* CachedLogicallyOrderedLeafBoxes::PreviousTextBox(
+ const RootInlineBox* root,
+ const InlineTextBox* box) {
+ if (!root)
+ return nullptr;
+
+ CollectBoxes(root);
+
+ // If box is null, root is box's previous RootInlineBox, and previousBox is
+ // the last logical box in root.
+ int box_index = leaf_boxes_.size() - 1;
+ if (box)
+ box_index = BoxIndexInLeaves(box) - 1;
+
+ for (int i = box_index; i >= 0; --i) {
+ if (leaf_boxes_[i]->IsInlineTextBox())
+ return ToInlineTextBox(leaf_boxes_[i]);
+ }
+
+ return nullptr;
+}
+
+const InlineTextBox* CachedLogicallyOrderedLeafBoxes::NextTextBox(
+ const RootInlineBox* root,
+ const InlineTextBox* box) {
+ if (!root)
+ return nullptr;
+
+ CollectBoxes(root);
+
+ // If box is null, root is box's next RootInlineBox, and nextBox is the first
+ // logical box in root. Otherwise, root is box's RootInlineBox, and nextBox is
+ // the next logical box in the same line.
+ size_t next_box_index = 0;
+ if (box)
+ next_box_index = BoxIndexInLeaves(box) + 1;
+
+ for (size_t i = next_box_index; i < leaf_boxes_.size(); ++i) {
+ if (leaf_boxes_[i]->IsInlineTextBox())
+ return ToInlineTextBox(leaf_boxes_[i]);
+ }
+
+ return nullptr;
+}
+
+const Vector<InlineBox*>& CachedLogicallyOrderedLeafBoxes::CollectBoxes(
+ const RootInlineBox* root) {
+ if (root_inline_box_ != root) {
+ root_inline_box_ = root;
+ leaf_boxes_.clear();
+ root->CollectLeafBoxesInLogicalOrder(leaf_boxes_);
+ }
+ return leaf_boxes_;
+}
+
+int CachedLogicallyOrderedLeafBoxes::BoxIndexInLeaves(
+ const InlineTextBox* box) const {
+ for (size_t i = 0; i < leaf_boxes_.size(); ++i) {
+ if (box == leaf_boxes_[i])
+ return i;
+ }
+ return 0;
+}
+
+const InlineTextBox* LogicallyPreviousBox(
+ const VisiblePosition& visible_position,
+ const InlineTextBox* text_box,
+ bool& previous_box_in_different_block,
+ CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
+ DCHECK(visible_position.IsValid()) << visible_position;
+ const InlineBox* start_box = text_box;
+
+ const InlineTextBox* previous_box =
+ leaf_boxes.PreviousTextBox(&start_box->Root(), text_box);
+ if (previous_box)
+ return previous_box;
+
+ previous_box =
+ leaf_boxes.PreviousTextBox(start_box->Root().PrevRootBox(), nullptr);
+ if (previous_box)
+ return previous_box;
+
+ for (;;) {
+ Node* start_node = start_box->GetLineLayoutItem().NonPseudoNode();
+ if (!start_node)
+ break;
+
+ Position position = PreviousRootInlineBoxCandidatePosition(
+ start_node, visible_position, kContentIsEditable);
+ if (position.IsNull())
+ break;
+
+ RenderedPosition rendered_position(position, TextAffinity::kDownstream);
+ RootInlineBox* previous_root = rendered_position.RootBox();
+ if (!previous_root)
+ break;
+
+ previous_box = leaf_boxes.PreviousTextBox(previous_root, nullptr);
+ if (previous_box) {
+ previous_box_in_different_block = true;
+ return previous_box;
+ }
+
+ if (!leaf_boxes.size())
+ break;
+ start_box = leaf_boxes.FirstBox();
+ }
+ return nullptr;
+}
+
+const InlineTextBox* LogicallyNextBox(
+ const VisiblePosition& visible_position,
+ const InlineTextBox* text_box,
+ bool& next_box_in_different_block,
+ CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
+ DCHECK(visible_position.IsValid()) << visible_position;
+ const InlineBox* start_box = text_box;
+
+ const InlineTextBox* next_box =
+ leaf_boxes.NextTextBox(&start_box->Root(), text_box);
+ if (next_box)
+ return next_box;
+
+ next_box = leaf_boxes.NextTextBox(start_box->Root().NextRootBox(), nullptr);
+ if (next_box)
+ return next_box;
+
+ for (;;) {
+ Node* start_node = start_box->GetLineLayoutItem().NonPseudoNode();
+ if (!start_node)
+ break;
+
+ Position position = NextRootInlineBoxCandidatePosition(
+ start_node, visible_position, kContentIsEditable);
+ if (position.IsNull())
+ break;
+
+ RenderedPosition rendered_position(position, TextAffinity::kDownstream);
+ RootInlineBox* next_root = rendered_position.RootBox();
+ if (!next_root)
+ break;
+
+ next_box = leaf_boxes.NextTextBox(next_root, nullptr);
+ if (next_box) {
+ next_box_in_different_block = true;
+ return next_box;
+ }
+
+ if (!leaf_boxes.size())
+ break;
+ start_box = leaf_boxes.FirstBox();
+ }
+ return nullptr;
+}
+
+TextBreakIterator* WordBreakIteratorForMinOffsetBoundary(
+ const VisiblePosition& visible_position,
+ const InlineTextBox* text_box,
+ int& previous_box_length,
+ bool& previous_box_in_different_block,
+ Vector<UChar, 1024>& string,
+ CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
+ DCHECK(visible_position.IsValid()) << visible_position;
+ previous_box_in_different_block = false;
+
+ // TODO(editing-dev) Handle the case when we don't have an inline text box.
+ const InlineTextBox* previous_box = LogicallyPreviousBox(
+ visible_position, text_box, previous_box_in_different_block, leaf_boxes);
+
+ int len = 0;
+ string.clear();
+ if (previous_box) {
+ previous_box_length = previous_box->Len();
+ previous_box->GetLineLayoutItem().GetText().AppendTo(
+ string, previous_box->Start(), previous_box_length);
+ len += previous_box_length;
+ }
+ text_box->GetLineLayoutItem().GetText().AppendTo(string, text_box->Start(),
+ text_box->Len());
+ len += text_box->Len();
+
+ return WordBreakIterator(string.data(), len);
+}
+
+TextBreakIterator* WordBreakIteratorForMaxOffsetBoundary(
+ const VisiblePosition& visible_position,
+ const InlineTextBox* text_box,
+ bool& next_box_in_different_block,
+ Vector<UChar, 1024>& string,
+ CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
+ DCHECK(visible_position.IsValid()) << visible_position;
+ next_box_in_different_block = false;
+
+ // TODO(editing-dev) Handle the case when we don't have an inline text box.
+ const InlineTextBox* next_box = LogicallyNextBox(
+ visible_position, text_box, next_box_in_different_block, leaf_boxes);
+
+ int len = 0;
+ string.clear();
+ text_box->GetLineLayoutItem().GetText().AppendTo(string, text_box->Start(),
+ text_box->Len());
+ len += text_box->Len();
+ if (next_box) {
+ next_box->GetLineLayoutItem().GetText().AppendTo(string, next_box->Start(),
+ next_box->Len());
+ len += next_box->Len();
+ }
+
+ return WordBreakIterator(string.data(), len);
+}
+
+bool IsLogicalStartOfWord(TextBreakIterator* iter,
+ int position,
+ bool hard_line_break) {
+ bool boundary = hard_line_break ? true : iter->isBoundary(position);
+ if (!boundary)
+ return false;
+
+ iter->following(position);
+ // isWordTextBreak returns true after moving across a word and false after
+ // moving across a punctuation/space.
+ return IsWordTextBreak(iter);
+}
+
+bool IslogicalEndOfWord(TextBreakIterator* iter,
+ int position,
+ bool hard_line_break) {
+ bool boundary = iter->isBoundary(position);
+ return (hard_line_break || boundary) && IsWordTextBreak(iter);
+}
+
+enum CursorMovementDirection { kMoveLeft, kMoveRight };
+
+VisiblePosition VisualWordPosition(const VisiblePosition& visible_position,
+ CursorMovementDirection direction,
+ bool skips_space_when_moving_right) {
+ DCHECK(visible_position.IsValid()) << visible_position;
+ if (visible_position.IsNull())
+ return VisiblePosition();
+
+ TextDirection block_direction =
+ DirectionOfEnclosingBlock(visible_position.DeepEquivalent());
+ InlineBox* previously_visited_box = nullptr;
+ VisiblePosition current = visible_position;
+ TextBreakIterator* iter = nullptr;
+
+ CachedLogicallyOrderedLeafBoxes leaf_boxes;
+ Vector<UChar, 1024> string;
+
+ for (;;) {
+ VisiblePosition adjacent_character_position = direction == kMoveRight
+ ? RightPositionOf(current)
+ : LeftPositionOf(current);
+ if (adjacent_character_position.DeepEquivalent() ==
+ current.DeepEquivalent() ||
+ adjacent_character_position.IsNull())
+ return VisiblePosition();
+
+ InlineBoxPosition box_position = ComputeInlineBoxPosition(
+ adjacent_character_position.DeepEquivalent(), TextAffinity::kUpstream);
+ InlineBox* box = box_position.inline_box;
+ int offset_in_box = box_position.offset_in_box;
+
+ if (!box)
+ break;
+ if (!box->IsInlineTextBox()) {
+ current = adjacent_character_position;
+ continue;
+ }
+
+ InlineTextBox* text_box = ToInlineTextBox(box);
+ int previous_box_length = 0;
+ bool previous_box_in_different_block = false;
+ bool next_box_in_different_block = false;
+ bool moving_into_new_box = previously_visited_box != box;
+
+ if (offset_in_box == box->CaretMinOffset()) {
+ iter = WordBreakIteratorForMinOffsetBoundary(
+ visible_position, text_box, previous_box_length,
+ previous_box_in_different_block, string, leaf_boxes);
+ } else if (offset_in_box == box->CaretMaxOffset()) {
+ iter = WordBreakIteratorForMaxOffsetBoundary(visible_position, text_box,
+ next_box_in_different_block,
+ string, leaf_boxes);
+ } else if (moving_into_new_box) {
+ iter = WordBreakIterator(text_box->GetLineLayoutItem().GetText(),
+ text_box->Start(), text_box->Len());
+ previously_visited_box = box;
+ }
+
+ if (!iter)
+ break;
+
+ iter->first();
+ int offset_in_iterator =
+ offset_in_box - text_box->Start() + previous_box_length;
+
+ bool is_word_break;
+ bool box_has_same_directionality_as_block =
+ box->Direction() == block_direction;
+ bool moving_backward =
+ (direction == kMoveLeft && box->Direction() == TextDirection::kLtr) ||
+ (direction == kMoveRight && box->Direction() == TextDirection::kRtl);
+ if ((skips_space_when_moving_right &&
+ box_has_same_directionality_as_block) ||
+ (!skips_space_when_moving_right && moving_backward)) {
+ bool logical_start_in_layout_object =
+ offset_in_box == static_cast<int>(text_box->Start()) &&
+ previous_box_in_different_block;
+ is_word_break = IsLogicalStartOfWord(iter, offset_in_iterator,
+ logical_start_in_layout_object);
+ } else {
+ bool logical_end_in_layout_object =
+ offset_in_box ==
+ static_cast<int>(text_box->Start() + text_box->Len()) &&
+ next_box_in_different_block;
+ is_word_break = IslogicalEndOfWord(iter, offset_in_iterator,
+ logical_end_in_layout_object);
+ }
+
+ if (is_word_break)
+ return adjacent_character_position;
+
+ current = adjacent_character_position;
+ }
+ return VisiblePosition();
+}
+
+} // namespace
+
+// TODO(yosin): Once we move |SelectionModifier::ModifyMovingLeft()| in this
+// file, we can make |LeftWordPosition()| as file local function.
+VisiblePosition LeftWordPosition(const VisiblePosition& visible_position,
+ bool skips_space_when_moving_right) {
+ DCHECK(visible_position.IsValid()) << visible_position;
+ VisiblePosition left_word_break = VisualWordPosition(
+ visible_position, kMoveLeft, skips_space_when_moving_right);
+ left_word_break = HonorEditingBoundaryAtOrBefore(
+ left_word_break, visible_position.DeepEquivalent());
+
+ // TODO(editing-dev) How should we handle a non-editable position?
+ if (left_word_break.IsNull() &&
+ IsEditablePosition(visible_position.DeepEquivalent())) {
+ TextDirection block_direction =
+ DirectionOfEnclosingBlock(visible_position.DeepEquivalent());
+ left_word_break = block_direction == TextDirection::kLtr
+ ? StartOfEditableContent(visible_position)
+ : EndOfEditableContent(visible_position);
+ }
+ return left_word_break;
+}
+
+// TODO(yosin): Once we move |SelectionModifier::ModifyMovingRight()| in this
+// file, we can make |RightWordPosition()| as file local function.
+VisiblePosition RightWordPosition(const VisiblePosition& visible_position,
+ bool skips_space_when_moving_right) {
+ DCHECK(visible_position.IsValid()) << visible_position;
+ VisiblePosition right_word_break = VisualWordPosition(
+ visible_position, kMoveRight, skips_space_when_moving_right);
+ right_word_break = HonorEditingBoundaryAtOrBefore(
+ right_word_break, visible_position.DeepEquivalent());
+
+ // TODO(editing-dev) How should we handle a non-editable position?
+ if (right_word_break.IsNull() &&
+ IsEditablePosition(visible_position.DeepEquivalent())) {
+ TextDirection block_direction =
+ DirectionOfEnclosingBlock(visible_position.DeepEquivalent());
+ right_word_break = block_direction == TextDirection::kLtr
+ ? EndOfEditableContent(visible_position)
+ : StartOfEditableContent(visible_position);
+ }
+ return right_word_break;
+}
+
+} // namespace blink
« no previous file with comments | « third_party/WebKit/Source/core/editing/BUILD.gn ('k') | third_party/WebKit/Source/core/editing/VisibleUnits.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698