handleCursorMovement method

void handleCursorMovement(
  1. LogicalKeyboardKey key, {
  2. required bool wordModifier,
  3. required bool lineModifier,
  4. required bool shift,
})
inherited

Implementation

void handleCursorMovement(
  LogicalKeyboardKey key, {
  required bool wordModifier,
  required bool lineModifier,
  required bool shift,
}) {
  if (wordModifier && lineModifier) {
    // If both modifiers are down, nothing happens on any of the platforms.
    return;
  }
  final selection = widget.controller.selection;

  TextSelection newSelection = widget.controller.selection;

  final plainText = textEditingValue.text;

  final bool rightArrow = key == LogicalKeyboardKey.arrowRight;
  final bool leftArrow = key == LogicalKeyboardKey.arrowLeft;
  final bool upArrow = key == LogicalKeyboardKey.arrowUp;
  final bool downArrow = key == LogicalKeyboardKey.arrowDown;

  if ((rightArrow || leftArrow) && !(rightArrow && leftArrow)) {
    // Jump to begin/end of word.
    if (wordModifier) {
      // If control/option is pressed, we will decide which way to look for a
      // word based on which arrow is pressed.
      if (leftArrow) {
        // When going left, we want to skip over any whitespace before the word,
        // so we go back to the first non-whitespace before asking for the word
        // boundary, since _selectWordAtOffset finds the word boundaries without
        // including whitespace.
        final int startPoint =
            previousCharacter(newSelection.extentOffset, plainText, false);
        final TextSelection textSelection = renderEditor
            .selectWordAtPosition(TextPosition(offset: startPoint));
        newSelection =
            newSelection.copyWith(extentOffset: textSelection.baseOffset);
      } else {
        // When going right, we want to skip over any whitespace after the word,
        // so we go forward to the first non-whitespace character before asking
        // for the word bounds, since _selectWordAtOffset finds the word
        // boundaries without including whitespace.
        final int startPoint =
            nextCharacter(newSelection.extentOffset, plainText, false);
        final TextSelection textSelection = renderEditor
            .selectWordAtPosition(TextPosition(offset: startPoint));
        newSelection =
            newSelection.copyWith(extentOffset: textSelection.extentOffset);
      }
    } else if (lineModifier) {
      // If control/command is pressed, we will decide which way to expand to
      // the beginning/end of the line based on which arrow is pressed.
      if (leftArrow) {
        // When going left.
        // This is not the optimal approach, see comment below for details.
        final int startPoint =
            previousCharacter(newSelection.extentOffset, plainText, false);
        final TextSelection textSelection = renderEditor
            .selectLineAtPosition(TextPosition(offset: startPoint));
        newSelection =
            newSelection.copyWith(extentOffset: textSelection.baseOffset);
      } else {
        // When going right, look to the right from current position until
        // we find the end of the line.
        // A better solution would be to take into account word wrapping and
        // only jump to the end of text before wrapping occurs.
        // TODO: Handle soft-wrapping
        // This can be implemented by getting vertical offset at the start
        // point, then getting selection boxes for this line, filtering to
        // only include boxes with the same vertical offset and getting the
        // largest right edge of all the remaining boxes.
        final int startPoint = newSelection.extentOffset;
        if (startPoint < plainText.length) {
          final TextSelection textSelection = renderEditor
              .selectLineAtPosition(TextPosition(offset: startPoint));
          newSelection =
              newSelection.copyWith(extentOffset: textSelection.extentOffset);
        }
      }
    } else {
      if (rightArrow && newSelection.extentOffset < plainText.length) {
        final int nextExtent =
            nextCharacter(newSelection.extentOffset, plainText);
        final int distance = nextExtent - newSelection.extentOffset;
        newSelection = newSelection.copyWith(extentOffset: nextExtent);
        if (shift) {
          _cursorResetLocation += distance;
        }
      } else if (leftArrow && newSelection.extentOffset > 0) {
        final int previousExtent =
            previousCharacter(newSelection.extentOffset, plainText);
        final int distance = newSelection.extentOffset - previousExtent;
        newSelection = newSelection.copyWith(extentOffset: previousExtent);
        if (shift) {
          _cursorResetLocation -= distance;
        }
      }
    }
  }

  // Handles moving the cursor vertically as well as taking care of the
  // case where the user moves the cursor to the end or beginning of the text
  // and then back up or down.
  if (downArrow || upArrow) {
    final originPosition = TextPosition(
        offset: upArrow ? selection.baseOffset : selection.extentOffset);

    final child = renderEditor.childAtPosition(originPosition);
    final localPosition = TextPosition(
        offset: originPosition.offset - child.node.documentOffset);

    TextPosition? position = upArrow
        ? child.getPositionAbove(localPosition)
        : child.getPositionBelow(localPosition);

    if (position == null) {
      // There was no text above/below in the current child, check the direct
      // sibling.
      final sibling = upArrow
          ? renderEditor.childBefore(child)
          : renderEditor.childAfter(child);
      if (sibling == null) {
        // reached beginning or end of the document, move to the
        // first/last character
        position = TextPosition(offset: upArrow ? 0 : plainText.length - 1);
      } else {
        final caretOffset = child.getOffsetForCaret(localPosition);
        final testPosition =
            TextPosition(offset: upArrow ? sibling.node.length - 1 : 0);
        final testOffset = sibling.getOffsetForCaret(testPosition);
        final finalOffset = Offset(caretOffset.dx, testOffset.dy);
        final siblingPosition = sibling.getPositionForOffset(finalOffset);
        position = TextPosition(
            offset: sibling.node.documentOffset + siblingPosition.offset);
      }
    } else {
      position =
          TextPosition(offset: child.node.documentOffset + position.offset);
    }

    // To account for the possibility where the user vertically highlights
    // all the way to the top or bottom of the text, we hold the previous
    // cursor location. This allows us to restore to this position in the
    // case that the user wants to unhighlight some text.
    if (position.offset == newSelection.extentOffset) {
      if (downArrow) {
        newSelection = newSelection.copyWith(extentOffset: plainText.length);
      } else if (upArrow) {
        newSelection = newSelection.copyWith(extentOffset: 0);
      }
      _wasSelectingVerticallyWithKeyboard = shift;
    } else if (_wasSelectingVerticallyWithKeyboard && shift) {
      newSelection =
          newSelection.copyWith(extentOffset: _cursorResetLocation);
      _wasSelectingVerticallyWithKeyboard = false;
    } else {
      newSelection = newSelection.copyWith(extentOffset: position.offset);
      _cursorResetLocation = newSelection.extentOffset;
    }
  }

  // Just place the collapsed selection at the end or beginning of the region
  // if shift isn't down.
  if (!shift) {
    // We want to put the cursor at the correct location depending on which
    // arrow is used while there is a selection.
    int newOffset = newSelection.extentOffset;
    if (!selection.isCollapsed) {
      if (leftArrow) {
        newOffset = newSelection.baseOffset < newSelection.extentOffset
            ? newSelection.baseOffset
            : newSelection.extentOffset;
      } else if (rightArrow) {
        newOffset = newSelection.baseOffset > newSelection.extentOffset
            ? newSelection.baseOffset
            : newSelection.extentOffset;
      }
    }
    newSelection =
        TextSelection.fromPosition(TextPosition(offset: newOffset));
  }

  widget.controller.updateSelection(newSelection, source: ChangeSource.local);
}