handleCursorMovement method
void
handleCursorMovement(
- LogicalKeyboardKey key, {
- required bool wordModifier,
- required bool lineModifier,
- 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);
}