Gracefully handle negative position in getWordAtOffset (#128464)
Should fix an unreproducible crash in text editing and track it with an assertion.
This commit is contained in:
parent
5cef69dd49
commit
16eb4f2c08
@ -2067,9 +2067,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||
void selectWordsInRange({ required Offset from, Offset? to, required SelectionChangedCause cause }) {
|
||||
_computeTextMetricsIfNeeded();
|
||||
final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset));
|
||||
final TextSelection fromWord = _getWordAtOffset(fromPosition);
|
||||
final TextSelection fromWord = getWordAtOffset(fromPosition);
|
||||
final TextPosition toPosition = to == null ? fromPosition : _textPainter.getPositionForOffset(globalToLocal(to - _paintOffset));
|
||||
final TextSelection toWord = toPosition == fromPosition ? fromWord : _getWordAtOffset(toPosition);
|
||||
final TextSelection toWord = toPosition == fromPosition ? fromWord : getWordAtOffset(toPosition);
|
||||
final bool isFromWordBeforeToWord = fromWord.start < toWord.end;
|
||||
|
||||
_setSelection(
|
||||
@ -2099,7 +2099,10 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||
_setSelection(newSelection, cause);
|
||||
}
|
||||
|
||||
TextSelection _getWordAtOffset(TextPosition position) {
|
||||
/// Returns a [TextSelection] that encompasses the word at the given
|
||||
/// [TextPosition].
|
||||
@visibleForTesting
|
||||
TextSelection getWordAtOffset(TextPosition position) {
|
||||
debugAssertLayoutUpToDate();
|
||||
// When long-pressing past the end of the text, we want a collapsed cursor.
|
||||
if (position.offset >= plainText.length) {
|
||||
@ -2120,6 +2123,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||
case TextAffinity.downstream:
|
||||
effectiveOffset = position.offset;
|
||||
}
|
||||
assert(effectiveOffset >= 0);
|
||||
|
||||
// On iOS, select the previous word if there is a previous word, or select
|
||||
// to the end of the next word if there is a next word. Select nothing if
|
||||
@ -2128,8 +2132,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||
// If the platform is Android and the text is read only, try to select the
|
||||
// previous word if there is one; otherwise, select the single whitespace at
|
||||
// the position.
|
||||
if (TextLayoutMetrics.isWhitespace(plainText.codeUnitAt(effectiveOffset))
|
||||
&& effectiveOffset > 0) {
|
||||
if (effectiveOffset > 0
|
||||
&& TextLayoutMetrics.isWhitespace(plainText.codeUnitAt(effectiveOffset))) {
|
||||
final TextRange? previousWord = _getPreviousWord(word.start);
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.iOS:
|
||||
|
@ -1823,6 +1823,52 @@ void main() {
|
||||
rrect: expectedRRect
|
||||
));
|
||||
});
|
||||
|
||||
test('getWordAtOffset with a negative position', () {
|
||||
const String text = 'abc';
|
||||
final _FakeEditableTextState delegate = _FakeEditableTextState()
|
||||
..textEditingValue = const TextEditingValue(text: text);
|
||||
final ViewportOffset viewportOffset = ViewportOffset.zero();
|
||||
final RenderEditable editable = RenderEditable(
|
||||
backgroundCursorColor: Colors.grey,
|
||||
selectionColor: Colors.black,
|
||||
textDirection: TextDirection.ltr,
|
||||
cursorColor: Colors.red,
|
||||
offset: viewportOffset,
|
||||
textSelectionDelegate: delegate,
|
||||
startHandleLayerLink: LayerLink(),
|
||||
endHandleLayerLink: LayerLink(),
|
||||
text: const TextSpan(
|
||||
text: text,
|
||||
style: TextStyle(height: 1.0, fontSize: 10.0),
|
||||
),
|
||||
);
|
||||
|
||||
layout(editable, onErrors: expectNoFlutterErrors);
|
||||
|
||||
// Cause text metrics to be computed.
|
||||
editable.computeDistanceToActualBaseline(TextBaseline.alphabetic);
|
||||
|
||||
final TextSelection selection;
|
||||
try {
|
||||
selection = editable.getWordAtOffset(const TextPosition(
|
||||
offset: -1,
|
||||
affinity: TextAffinity.upstream,
|
||||
));
|
||||
} catch (error) {
|
||||
// In debug mode, negative offsets are caught by an assertion.
|
||||
expect(error, isA<AssertionError>());
|
||||
return;
|
||||
}
|
||||
|
||||
// Web's Paragraph.getWordBoundary behaves differently for a negative
|
||||
// position.
|
||||
if (kIsWeb) {
|
||||
expect(selection, const TextSelection.collapsed(offset: 0));
|
||||
} else {
|
||||
expect(selection, const TextSelection.collapsed(offset: text.length));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class _TestRenderEditable extends RenderEditable {
|
||||
|
Loading…
x
Reference in New Issue
Block a user