Skip to content

feat: Add allowSpacesInSuggestions parameter to AutocompleteTrigger #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 45 additions & 8 deletions lib/src/autocomplete_trigger.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:multi_trigger_autocomplete/src/autocomplete_query.dart';

Expand All @@ -17,20 +19,37 @@ class AutocompleteTrigger {
required this.optionsViewBuilder,
this.triggerOnlyAtStart = false,
this.triggerOnlyAfterSpace = true,
this.allowSpacesInSuggestions = false,
this.minimumRequiredCharacters = 0,
});
this.triggerSet,
}) : assert(
!(allowSpacesInSuggestions && triggerSet == null),
'Error: Triggers cannot be empty if allowSpacesInSuggestions is true.',
);

/// The trigger character.
///
/// eg. '@', '#', ':'
final String trigger;

/// All trigger characters.
/// Needed if [allowSpacesInSuggestions] is set to true.
///
/// eg. {'@', '#', ':'}
final Set<String>? triggerSet;

/// Whether the [trigger] should only be recognised at the start of the input.
final bool triggerOnlyAtStart;

/// 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;
Expand Down Expand Up @@ -68,12 +87,25 @@ class AutocompleteTrigger {
if (!selection.isValid) return null;
final cursorPosition = selection.baseOffset;

// Find the first [trigger] location before the input cursor.
// 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(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;
if (firstTriggerIndexBeforeCursor == -1) {
return null;
}

// If the [trigger] is not at [firstTriggerIndexBeforeCursor], then it's not a trigger.
final triggerFromText = text.substring(
firstTriggerIndexBeforeCursor,
min(firstTriggerIndexBeforeCursor + trigger.length, text.length),
);
if (triggerFromText != 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.
Expand All @@ -97,11 +129,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.
Expand Down
62 changes: 62 additions & 0 deletions test/autocomplete_trigger_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -297,4 +297,66 @@ void main() {
expect(trigger1, isNot(trigger2));
});
});

group('Autocomplete trigger with and without `allowSpacesInSuggestions`', () {
final triggerWithSpaces = AutocompleteTrigger(
trigger: '@',
allowSpacesInSuggestions: true,
triggerSet: {'@'},
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);
},
);
});
}