From 422ba71f04bdadb5eefc56a47f72849882e0e740 Mon Sep 17 00:00:00 2001 From: biongoo Date: Mon, 19 Jun 2023 12:09:00 +0200 Subject: [PATCH 1/5] add allowSpacesInSuggestions param --- lib/src/autocomplete_trigger.dart | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/src/autocomplete_trigger.dart b/lib/src/autocomplete_trigger.dart index 73ebcdf..5545b88 100644 --- a/lib/src/autocomplete_trigger.dart +++ b/lib/src/autocomplete_trigger.dart @@ -17,6 +17,7 @@ class AutocompleteTrigger { required this.optionsViewBuilder, this.triggerOnlyAtStart = false, this.triggerOnlyAfterSpace = true, + this.allowSpacesInSuggestions = false, this.minimumRequiredCharacters = 0, }); @@ -31,6 +32,12 @@ class AutocompleteTrigger { /// Whether the [trigger] should only be recognised after a space. final bool triggerOnlyAfterSpace; + /// Whether the [trigger] should recognise autocomplete options + /// containing spaces. If set to true, suggestions like "@luke skywalker" + /// would be considered valid. If set to false, the first space character + /// would end the suggestion. + final bool allowSpacesInSuggestions; + /// The minimum required characters for the [trigger] to start recognising /// a autocomplete options. final int minimumRequiredCharacters; @@ -97,11 +104,16 @@ class AutocompleteTrigger { final suggestionEnd = cursorPosition; if (suggestionStart > suggestionEnd) return null; - // Fetch the suggestion text. The suggestions can't have spaces. - // valid example: "@luke_skywa..." - // invalid example: "@luke skywa..." + // Fetch the suggestion text. final suggestionText = text.substring(suggestionStart, suggestionEnd); - if (suggestionText.contains(' ')) return null; + + // If [allowSpacesInSuggestions] is false, the suggestions can't have spaces. + // If true, suggestions like "@luke skywalker" would be considered valid. + // If false, suggestions like "@luke skywalker" would be considered invalid, + // and only examples like "@luke_skywalker" would be valid. + if (!allowSpacesInSuggestions && suggestionText.contains(' ')) { + return null; + } // A minimum number of characters can be provided to only show // suggestions after the customer has input enough characters. From 7b44c42a04423d2547459dde9910ad19f7e170b1 Mon Sep 17 00:00:00 2001 From: biongoo Date: Mon, 19 Jun 2023 12:16:12 +0200 Subject: [PATCH 2/5] add tests to allowSpacesInSuggestions param --- test/autocomplete_trigger_test.dart | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/autocomplete_trigger_test.dart b/test/autocomplete_trigger_test.dart index 974db2a..3344f44 100644 --- a/test/autocomplete_trigger_test.dart +++ b/test/autocomplete_trigger_test.dart @@ -297,4 +297,65 @@ void main() { expect(trigger1, isNot(trigger2)); }); }); + + group('Autocomplete trigger with and without `allowSpacesInSuggestions`', () { + final triggerWithSpaces = AutocompleteTrigger( + trigger: '@', + allowSpacesInSuggestions: true, + optionsViewBuilder: ( + context, + autocompleteQuery, + textEditingController, + ) { + return const SizedBox.shrink(); + }, + ); + + final triggerWithoutSpaces = AutocompleteTrigger( + trigger: '@', + allowSpacesInSuggestions: false, + optionsViewBuilder: ( + context, + autocompleteQuery, + textEditingController, + ) { + return const SizedBox.shrink(); + }, + ); + + test( + 'should return query if `@` is invoked and the word contains spaces when `allowSpacesInSuggestions` is true', + () { + const text = 'Hey @Sahil Kumar'; + final invoked = triggerWithSpaces.invokingTrigger( + const TextEditingValue( + text: text, + selection: TextSelection.collapsed(offset: text.length), + ), + ); + + expect(invoked, isNotNull); + expect(invoked!.query, 'Sahil Kumar'); + expect( + invoked.selection, + const TextSelection(baseOffset: 5, extentOffset: 16), + ); + }, + ); + + test( + "should return null if `@` is invoked and the word contains spaces when `allowSpacesInSuggestions` is false", + () { + const text = 'Hey @Sahil Kumar'; + final invoked = triggerWithoutSpaces.invokingTrigger( + const TextEditingValue( + text: text, + selection: TextSelection.collapsed(offset: text.length), + ), + ); + + expect(invoked, isNull); + }, + ); + }); } From b659265502ccffc1184ab07008ae1d0c09d3ea65 Mon Sep 17 00:00:00 2001 From: biongoo Date: Wed, 21 Jun 2023 14:14:00 +0200 Subject: [PATCH 3/5] fix problem with multiple mentions using different triggers --- lib/src/autocomplete_trigger.dart | 25 +++++++++++++++++++++---- test/autocomplete_trigger_test.dart | 1 + 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/src/autocomplete_trigger.dart b/lib/src/autocomplete_trigger.dart index 5545b88..13851e4 100644 --- a/lib/src/autocomplete_trigger.dart +++ b/lib/src/autocomplete_trigger.dart @@ -12,20 +12,32 @@ typedef AutocompleteTriggerOptionsViewBuilder = Widget Function( class AutocompleteTrigger { /// Creates a [AutocompleteTrigger] which can be used to trigger /// autocomplete suggestions. - const AutocompleteTrigger({ + AutocompleteTrigger({ required this.trigger, required this.optionsViewBuilder, this.triggerOnlyAtStart = false, this.triggerOnlyAfterSpace = true, this.allowSpacesInSuggestions = false, this.minimumRequiredCharacters = 0, - }); + Set? triggers, + }) { + assert( + !(allowSpacesInSuggestions && triggers == null), + 'Error: Triggers cannot be empty if allowSpacesInSuggestions is true.', + ); + triggerSet = triggers ?? {trigger}; + } /// The trigger character. /// /// eg. '@', '#', ':' final String trigger; + /// All trigger characters. + /// + /// eg. {'@', '#', ':'} + late final Set triggerSet; + /// Whether the [trigger] should only be recognised at the start of the input. final bool triggerOnlyAtStart; @@ -76,11 +88,16 @@ class AutocompleteTrigger { final cursorPosition = selection.baseOffset; // Find the first [trigger] location before the input cursor. + final triggersRegExp = RegExp(triggerSet.join('|')); final firstTriggerIndexBeforeCursor = - text.substring(0, cursorPosition).lastIndexOf(trigger); + text.substring(0, cursorPosition).lastIndexOf(triggersRegExp); // If the [trigger] is not found before the cursor, then it's not a trigger. - if (firstTriggerIndexBeforeCursor == -1) return null; + // or the [trigger] is not at [firstTriggerIndexBeforeCursor] + if (firstTriggerIndexBeforeCursor == -1 || + text[firstTriggerIndexBeforeCursor] != trigger) { + return null; + } // If the [trigger] is found before the cursor, but the [trigger] is only // recognised at the start of the input, then it's not a trigger. diff --git a/test/autocomplete_trigger_test.dart b/test/autocomplete_trigger_test.dart index 3344f44..f558e07 100644 --- a/test/autocomplete_trigger_test.dart +++ b/test/autocomplete_trigger_test.dart @@ -302,6 +302,7 @@ void main() { final triggerWithSpaces = AutocompleteTrigger( trigger: '@', allowSpacesInSuggestions: true, + triggers: {'@'}, optionsViewBuilder: ( context, autocompleteQuery, From f0fabf695eafab60c0a6824f261d76e40484cfbf Mon Sep 17 00:00:00 2001 From: biongoo Date: Tue, 27 Jun 2023 16:23:13 +0200 Subject: [PATCH 4/5] fix problem with multiple character triggers --- lib/src/autocomplete_trigger.dart | 34 ++++++++++++++++------------- test/autocomplete_trigger_test.dart | 2 +- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/src/autocomplete_trigger.dart b/lib/src/autocomplete_trigger.dart index 13851e4..fc0b1eb 100644 --- a/lib/src/autocomplete_trigger.dart +++ b/lib/src/autocomplete_trigger.dart @@ -12,21 +12,18 @@ typedef AutocompleteTriggerOptionsViewBuilder = Widget Function( class AutocompleteTrigger { /// Creates a [AutocompleteTrigger] which can be used to trigger /// autocomplete suggestions. - AutocompleteTrigger({ + const AutocompleteTrigger({ required this.trigger, required this.optionsViewBuilder, this.triggerOnlyAtStart = false, this.triggerOnlyAfterSpace = true, this.allowSpacesInSuggestions = false, this.minimumRequiredCharacters = 0, - Set? triggers, - }) { - assert( - !(allowSpacesInSuggestions && triggers == null), - 'Error: Triggers cannot be empty if allowSpacesInSuggestions is true.', - ); - triggerSet = triggers ?? {trigger}; - } + this.triggerSet, + }) : assert( + !(allowSpacesInSuggestions && triggerSet == null), + 'Error: Triggers cannot be empty if allowSpacesInSuggestions is true.', + ); /// The trigger character. /// @@ -34,9 +31,10 @@ class AutocompleteTrigger { final String trigger; /// All trigger characters. + /// Needed if [allowSpacesInSuggestions] is set to true. /// /// eg. {'@', '#', ':'} - late final Set triggerSet; + final Set? triggerSet; /// Whether the [trigger] should only be recognised at the start of the input. final bool triggerOnlyAtStart; @@ -87,15 +85,21 @@ class AutocompleteTrigger { if (!selection.isValid) return null; final cursorPosition = selection.baseOffset; - // Find the first [trigger] location before the input cursor. - final triggersRegExp = RegExp(triggerSet.join('|')); + // Find the first [triggerSet] item location before the input cursor. + final triggersRegExp = RegExp( + (triggerSet ?? {trigger}).map((e) => RegExp.escape(e)).join('|')); final firstTriggerIndexBeforeCursor = text.substring(0, cursorPosition).lastIndexOf(triggersRegExp); // If the [trigger] is not found before the cursor, then it's not a trigger. - // or the [trigger] is not at [firstTriggerIndexBeforeCursor] - if (firstTriggerIndexBeforeCursor == -1 || - text[firstTriggerIndexBeforeCursor] != trigger) { + if (firstTriggerIndexBeforeCursor == -1) { + return null; + } + + // If the [trigger] is not at [firstTriggerIndexBeforeCursor], then it's not a trigger. + final triggerFromText = text.substring(firstTriggerIndexBeforeCursor, + firstTriggerIndexBeforeCursor + trigger.length); + if (triggerFromText != trigger) { return null; } diff --git a/test/autocomplete_trigger_test.dart b/test/autocomplete_trigger_test.dart index f558e07..41ee453 100644 --- a/test/autocomplete_trigger_test.dart +++ b/test/autocomplete_trigger_test.dart @@ -302,7 +302,7 @@ void main() { final triggerWithSpaces = AutocompleteTrigger( trigger: '@', allowSpacesInSuggestions: true, - triggers: {'@'}, + triggerSet: {'@'}, optionsViewBuilder: ( context, autocompleteQuery, From 9d8a9421e29207e361b36fea553cfe1a9539bfc6 Mon Sep 17 00:00:00 2001 From: biongoo Date: Tue, 27 Jun 2023 19:23:42 +0200 Subject: [PATCH 5/5] fix index out of range --- lib/src/autocomplete_trigger.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/autocomplete_trigger.dart b/lib/src/autocomplete_trigger.dart index fc0b1eb..009a570 100644 --- a/lib/src/autocomplete_trigger.dart +++ b/lib/src/autocomplete_trigger.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:multi_trigger_autocomplete/src/autocomplete_query.dart'; @@ -97,8 +99,10 @@ class AutocompleteTrigger { } // If the [trigger] is not at [firstTriggerIndexBeforeCursor], then it's not a trigger. - final triggerFromText = text.substring(firstTriggerIndexBeforeCursor, - firstTriggerIndexBeforeCursor + trigger.length); + final triggerFromText = text.substring( + firstTriggerIndexBeforeCursor, + min(firstTriggerIndexBeforeCursor + trigger.length, text.length), + ); if (triggerFromText != trigger) { return null; }