From 1b62b805ca73a105a8a24dd2f3c869a2d4e54d6b Mon Sep 17 00:00:00 2001 From: Gary Qian Date: Tue, 16 Jun 2020 10:20:04 -0700 Subject: [PATCH] Handle backspace edgecase in trailing whitespace formatter. (#59379) --- .../lib/src/widgets/editable_text.dart | 18 +++++++-- .../test/widgets/editable_text_test.dart | 40 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index f3ea4b121c..9cd3566e1c 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -2571,9 +2571,14 @@ class _WhitespaceDirectionalityFormatter extends TextInputFormatter { composingEnd -= outputCodepoints.length < composingEnd ? 1 : 0; } + final bool isBackspace = oldValue.text.runes.length - newValue.text.runes.length == 1 && + isDirectionalityMarker(oldValue.text.runes.last) && + oldValue.text.substring(0, oldValue.text.length - 1) == newValue.text; + bool previousWasWhitespace = false; bool previousWasDirectionalityMarker = false; int previousNonWhitespaceCodepoint; + int index = 0; for (final int codepoint in newValue.text.runes) { if (isWhitespace(codepoint)) { // Only compute the directionality of the non-whitespace @@ -2587,9 +2592,15 @@ class _WhitespaceDirectionalityFormatter extends TextInputFormatter { subtractFromLength(); outputCodepoints.removeLast(); } - outputCodepoints.add(codepoint); - addToLength(); - outputCodepoints.add(_previousNonWhitespaceDirection == TextDirection.rtl ? _rlm : _lrm); + // Handle trailing whitespace deleting the directionality char instead of the whitespace. + if (isBackspace && index == newValue.text.runes.length - 1) { + // Do not append the whitespace to the outputCodepoints. + subtractFromLength(); + } else { + outputCodepoints.add(codepoint); + addToLength(); + outputCodepoints.add(_previousNonWhitespaceDirection == TextDirection.rtl ? _rlm : _lrm); + } previousWasWhitespace = true; previousWasDirectionalityMarker = false; @@ -2620,6 +2631,7 @@ class _WhitespaceDirectionalityFormatter extends TextInputFormatter { previousWasWhitespace = false; previousWasDirectionalityMarker = false; } + index++; } final String formatted = String.fromCharCodes(outputCodepoints); return TextEditingValue( diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 4083ec71ab..090651aaa4 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -4923,6 +4923,46 @@ void main() { expect(state.currentTextEditingValue.text, equals('\u{200E}🇧🇼🇧🇷🇮🇴 🇻🇬🇧🇳wahhh!🇧🇬🇧🇫 🇧🇮🇰🇭عَ عَ \u{200F}🇨🇲 🇨🇦🇮🇨 🇨🇻🇧🇶 🇰🇾🇨🇫 🇹🇩🇨🇱 🇨🇳🇨🇽\u{200F}')); }); + testWidgets('Whitespace directionality formatter handles deletion of trailing whitespace', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(text: 'testText'); + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(devicePixelRatio: 1.0), + child: Directionality( + textDirection: TextDirection.ltr, + child: FocusScope( + node: focusScopeNode, + autofocus: true, + child: EditableText( + backgroundCursorColor: Colors.blue, + controller: controller, + focusNode: focusNode, + maxLines: 1, // Sets text keyboard implicitly. + style: textStyle, + cursorColor: cursorColor, + ), + ), + ), + ), + ); + + await tester.tap(find.byType(EditableText)); + await tester.showKeyboard(find.byType(EditableText)); + controller.text = ''; + await tester.idle(); + + final EditableTextState state = + tester.state(find.byType(EditableText)); + expect(tester.testTextInput.editingState['text'], equals('')); + expect(state.wantKeepAlive, true); + + // Simulate deleting only the trailing RTL mark. + state.updateEditingValue(const TextEditingValue(text: 'hello \u{200E}الْعَ بِيَّةُ \u{200F}')); + state.updateEditingValue(const TextEditingValue(text: 'hello \u{200E}الْعَ بِيَّةُ ')); + // The trailing space should be gone here. + expect(state.currentTextEditingValue.text, equals('hello \u{200E}الْعَ بِيَّةُ')); + }); + testWidgets('EditableText changes mouse cursor when hovered', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery(