diff --git a/CHANGELOG.md b/CHANGELOG.md index 84cc605..870e0b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## 1.0.1 - Fix: recheck text on changing language +- Fixes a bug where changing text programmatically can sometimes throw a RangeError +- Fixes notifying listeners after async gap when disposed ## 1.0.0 diff --git a/lib/src/core/controllers/language_tool_controller.dart b/lib/src/core/controllers/language_tool_controller.dart index 0e7e999..82ad00e 100644 --- a/lib/src/core/controllers/language_tool_controller.dart +++ b/lib/src/core/controllers/language_tool_controller.dart @@ -12,6 +12,7 @@ import 'package:languagetool_textfield/src/utils/keep_latest_response_service.da /// marked TextSpans with tap recognizer class LanguageToolController extends TextEditingController { bool _isEnabled; + bool _isDisposed = false; /// Color scheme to highlight mistakes final HighlightStyle highlightStyle; @@ -59,7 +60,7 @@ class LanguageToolController extends TextEditingController { bool get isEnabled => _isEnabled; set isEnabled(bool value) { - if (value == _isEnabled) return; + if (value == _isEnabled || _isDisposed) return; _isEnabled = value; @@ -126,9 +127,11 @@ class LanguageToolController extends TextEditingController { }) { final languageToolService = LanguageToolService(languageToolClient); - // false positive, the same variable is used beyond the return statement - // ignore: avoid_unnecessary_return_variable - if (delay == Duration.zero) return languageToolService; + if (delay == Duration.zero) { + // false positive, the variable might be used after the if statement + // ignore: avoid_unnecessary_return_variable + return languageToolService; + } switch (delayType) { case DelayType.debouncing: @@ -168,6 +171,7 @@ class LanguageToolController extends TextEditingController { @override void dispose() { + _isDisposed = true; _languageCheckService?.dispose(); super.dispose(); } @@ -198,8 +202,7 @@ class LanguageToolController extends TextEditingController { ///set value triggers each time, even when cursor changes its location ///so this check avoid cleaning Mistake list when text wasn't really changed if (spellCheckSameText || newText != text && newText.isNotEmpty) { - final filteredMistakes = _filterMistakesOnChanged(newText); - _mistakes = filteredMistakes.toList(); + _mistakes = _filterMistakesOnChanged(_mistakes, newText); // If we have a text change and we have a popup on hold // it will close the popup @@ -215,7 +218,11 @@ class LanguageToolController extends TextEditingController { () => _languageCheckService?.findMistakes(newText) ?? Future(() => null), ); - if (mistakesWrapper == null || !mistakesWrapper.hasResult) return; + if (mistakesWrapper == null || + !mistakesWrapper.hasResult || + _isDisposed) { + return; + } final mistakes = mistakesWrapper.result(); _fetchError = mistakesWrapper.error; @@ -310,27 +317,37 @@ class LanguageToolController extends TextEditingController { ); } - /// Filters the list of mistakes based on the changes - /// in the text when it is changed. - Iterable _filterMistakesOnChanged(String newText) sync* { + List _filterMistakesOnChanged( + List mistakes, + String newText, + ) { + if (!selection.isValid) { + return mistakes + .where((mistake) => mistake.endOffset <= newText.length) + .toList(); + } + final isSelectionRangeEmpty = selection.end == selection.start; final lengthDiscrepancy = newText.length - text.length; - for (final mistake in _mistakes) { - Mistake? newMistake; - - newMistake = isSelectionRangeEmpty - ? _adjustMistakeOffsetWithCaretCursor( - mistake: mistake, - lengthDiscrepancy: lengthDiscrepancy, - ) - : _adjustMistakeOffsetWithSelectionRange( - mistake: mistake, - lengthDiscrepancy: lengthDiscrepancy, - ); - - if (newMistake != null) yield newMistake; - } + return mistakes + .map( + (mistake) => isSelectionRangeEmpty + ? _adjustMistakeOffsetWithCaretCursor( + mistake: mistake, + lengthDiscrepancy: lengthDiscrepancy, + ) + : _adjustMistakeOffsetWithSelectionRange( + mistake: mistake, + lengthDiscrepancy: lengthDiscrepancy, + ), + ) + .nonNulls + .where( + (mistake) => + mistake.offset >= 0 && mistake.endOffset <= newText.length, + ) + .toList(); } /// Adjusts the mistake offset when the selection is a caret cursor. diff --git a/lib/src/utils/mistake_popup.dart b/lib/src/utils/mistake_popup.dart index 14a1f92..f16d6b6 100644 --- a/lib/src/utils/mistake_popup.dart +++ b/lib/src/utils/mistake_popup.dart @@ -90,6 +90,7 @@ class LanguageToolMistakePopup extends StatelessWidget { required this.mistake, required this.controller, required this.mistakePosition, + super.key, this.maxWidth = _defaultMaxWidth, this.maxHeight = double.infinity, this.horizontalMargin = _defaultHorizontalMargin,