| Index: ui/gfx/render_text.cc
|
| diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc
|
| index 645573865c3d075a7389a54d3c0dc5277658fdb7..37e9721f0220996c05126424f9978f6c47a3714e 100644
|
| --- a/ui/gfx/render_text.cc
|
| +++ b/ui/gfx/render_text.cc
|
| @@ -501,26 +501,28 @@ void RenderText::SetCursorPosition(size_t position) {
|
| void RenderText::MoveCursor(BreakType break_type,
|
| VisualCursorDirection direction,
|
| bool select) {
|
| - SelectionModel position(cursor_position(), selection_model_.caret_affinity());
|
| + SelectionModel cursor(cursor_position(), selection_model_.caret_affinity());
|
| // Cancelling a selection moves to the edge of the selection.
|
| if (break_type != LINE_BREAK && !selection().is_empty() && !select) {
|
| SelectionModel selection_start = GetSelectionModelForSelectionStart();
|
| int start_x = GetCursorBounds(selection_start, true).x();
|
| - int cursor_x = GetCursorBounds(position, true).x();
|
| + int cursor_x = GetCursorBounds(cursor, true).x();
|
| // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
|
| // or right (when |direction| is CURSOR_RIGHT) of the selection end.
|
| if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x)
|
| - position = selection_start;
|
| - // For word breaks, use the nearest word boundary in the appropriate
|
| - // |direction|.
|
| + cursor = selection_start;
|
| + // Use the nearest word boundary in the proper |direction| for word breaks.
|
| if (break_type == WORD_BREAK)
|
| - position = GetAdjacentSelectionModel(position, break_type, direction);
|
| + cursor = GetAdjacentSelectionModel(cursor, break_type, direction);
|
| + // Use an adjacent selection model if the cursor is not at a valid position.
|
| + if (!IsValidCursorIndex(cursor.caret_pos()))
|
| + cursor = GetAdjacentSelectionModel(cursor, CHARACTER_BREAK, direction);
|
| } else {
|
| - position = GetAdjacentSelectionModel(position, break_type, direction);
|
| + cursor = GetAdjacentSelectionModel(cursor, break_type, direction);
|
| }
|
| if (select)
|
| - position.set_selection_start(selection().start());
|
| - MoveCursorTo(position);
|
| + cursor.set_selection_start(selection().start());
|
| + MoveCursorTo(cursor);
|
| }
|
|
|
| bool RenderText::MoveCursorTo(const SelectionModel& model) {
|
| @@ -528,9 +530,8 @@ bool RenderText::MoveCursorTo(const SelectionModel& model) {
|
| size_t text_length = text().length();
|
| Range range(std::min(model.selection().start(), text_length),
|
| std::min(model.caret_pos(), text_length));
|
| - // The current model only supports caret positions at valid character indices.
|
| - if (!IsCursorablePosition(range.start()) ||
|
| - !IsCursorablePosition(range.end()))
|
| + // The current model only supports caret positions at valid cursor indices.
|
| + if (!IsValidCursorIndex(range.start()) || !IsValidCursorIndex(range.end()))
|
| return false;
|
| SelectionModel sel(range, model.caret_affinity());
|
| bool changed = sel != selection_model_;
|
| @@ -548,7 +549,8 @@ bool RenderText::MoveCursorTo(const Point& point, bool select) {
|
| bool RenderText::SelectRange(const Range& range) {
|
| Range sel(std::min(range.start(), text().length()),
|
| std::min(range.end(), text().length()));
|
| - if (!IsCursorablePosition(sel.start()) || !IsCursorablePosition(sel.end()))
|
| + // Allow selection bounds at valid indicies amid multi-character graphemes.
|
| + if (!IsValidLogicalIndex(sel.start()) || !IsValidLogicalIndex(sel.end()))
|
| return false;
|
| LogicalCursorDirection affinity =
|
| (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD;
|
| @@ -767,6 +769,21 @@ void RenderText::DrawCursor(Canvas* canvas, const SelectionModel& position) {
|
| canvas->FillRect(GetCursorBounds(position, true), cursor_color_);
|
| }
|
|
|
| +bool RenderText::IsValidLogicalIndex(size_t index) {
|
| + // Check that the index is at a valid code point (not mid-surrgate-pair) and
|
| + // that it's not truncated from the layout text (its glyph may be shown).
|
| + //
|
| + // Indices within truncated text are disallowed so users can easily interact
|
| + // with the underlying truncated text using the ellipsis as a proxy. This lets
|
| + // users select all text, select the truncated text, and transition from the
|
| + // last rendered glyph to the end of the text without getting invisible cursor
|
| + // positions nor needing unbounded arrow key presses to traverse the ellipsis.
|
| + return index == 0 || index == text().length() ||
|
| + (index < text().length() &&
|
| + (truncate_length_ == 0 || index < truncate_length_) &&
|
| + IsValidCodePointIndex(text(), index));
|
| +}
|
| +
|
| Rect RenderText::GetCursorBounds(const SelectionModel& caret,
|
| bool insert_mode) {
|
| // TODO(ckocagil): Support multiline. This function should return the height
|
| @@ -774,9 +791,8 @@ Rect RenderText::GetCursorBounds(const SelectionModel& caret,
|
| // the multiline size, eliminate its use here.
|
|
|
| EnsureLayout();
|
| -
|
| size_t caret_pos = caret.caret_pos();
|
| - DCHECK(IsCursorablePosition(caret_pos));
|
| + DCHECK(IsValidLogicalIndex(caret_pos));
|
| // In overtype mode, ignore the affinity and always indicate that we will
|
| // overtype the next character.
|
| LogicalCursorDirection caret_affinity =
|
| @@ -817,7 +833,7 @@ size_t RenderText::IndexOfAdjacentGrapheme(size_t index,
|
| if (direction == CURSOR_FORWARD) {
|
| while (index < text().length()) {
|
| index++;
|
| - if (IsCursorablePosition(index))
|
| + if (IsValidCursorIndex(index))
|
| return index;
|
| }
|
| return text().length();
|
| @@ -825,7 +841,7 @@ size_t RenderText::IndexOfAdjacentGrapheme(size_t index,
|
|
|
| while (index > 0) {
|
| index--;
|
| - if (IsCursorablePosition(index))
|
| + if (IsValidCursorIndex(index))
|
| return index;
|
| }
|
| return 0;
|
| @@ -1108,7 +1124,7 @@ bool RenderText::RangeContainsCaret(const Range& range,
|
|
|
| void RenderText::MoveCursorTo(size_t position, bool select) {
|
| size_t cursor = std::min(position, text().length());
|
| - if (IsCursorablePosition(cursor))
|
| + if (IsValidCursorIndex(cursor))
|
| SetSelectionModel(SelectionModel(
|
| Range(select ? selection().start() : cursor, cursor),
|
| (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD));
|
|
|