Improve FilteringCommandSuggestionProcessor and adjust default filters (#410)
This commit is contained in:
parent
6c026f994b
commit
eca81f7372
10 changed files with 278 additions and 8 deletions
|
|
@ -108,7 +108,8 @@ public abstract class CommandManager<C> {
|
|||
|
||||
private CaptionVariableReplacementHandler captionVariableReplacementHandler = new SimpleCaptionVariableReplacementHandler();
|
||||
private CommandSyntaxFormatter<C> commandSyntaxFormatter = new StandardCommandSyntaxFormatter<>();
|
||||
private CommandSuggestionProcessor<C> commandSuggestionProcessor = new FilteringCommandSuggestionProcessor<>();
|
||||
private CommandSuggestionProcessor<C> commandSuggestionProcessor =
|
||||
new FilteringCommandSuggestionProcessor<>(FilteringCommandSuggestionProcessor.Filter.startsWith(true));
|
||||
private CommandRegistrationHandler commandRegistrationHandler;
|
||||
private CaptionRegistry<C> captionRegistry;
|
||||
private final AtomicReference<RegistrationState> state = new AtomicReference<>(RegistrationState.BEFORE_REGISTRATION);
|
||||
|
|
|
|||
|
|
@ -38,4 +38,16 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
public interface CommandSuggestionProcessor<C> extends
|
||||
BiFunction<@NonNull CommandPreprocessingContext<C>, @NonNull List<String>, @NonNull List<String>> {
|
||||
|
||||
/**
|
||||
* Create a pass through {@link CommandSuggestionProcessor} that simply returns
|
||||
* the input.
|
||||
*
|
||||
* @param <C> sender type
|
||||
* @return new processor
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
static <C> @NonNull CommandSuggestionProcessor<C> passThrough() {
|
||||
return (ctx, suggestions) -> suggestions;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,19 +24,46 @@
|
|||
package cloud.commandframework.execution;
|
||||
|
||||
import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import org.apiguardian.api.API;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Command suggestion processor that checks the input queue head and filters based on that
|
||||
* Command suggestion processor filters suggestions based on the remaining unconsumed input in the
|
||||
* queue.
|
||||
*
|
||||
* @param <C> Command sender type
|
||||
*/
|
||||
@API(status = API.Status.STABLE)
|
||||
public final class FilteringCommandSuggestionProcessor<C> implements CommandSuggestionProcessor<C> {
|
||||
|
||||
private final @NonNull Filter<C> filter;
|
||||
|
||||
/**
|
||||
* Create a new {@link FilteringCommandSuggestionProcessor} filtering with {@link String#startsWith(String)} that does
|
||||
* not ignore case.
|
||||
*/
|
||||
@API(status = API.Status.STABLE)
|
||||
public FilteringCommandSuggestionProcessor() {
|
||||
this(Filter.startsWith(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FilteringCommandSuggestionProcessor}.
|
||||
*
|
||||
* @param filter mode
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
public FilteringCommandSuggestionProcessor(final @NonNull Filter<C> filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<@NonNull String> apply(
|
||||
final @NonNull CommandPreprocessingContext<C> context,
|
||||
|
|
@ -46,14 +73,217 @@ public final class FilteringCommandSuggestionProcessor<C> implements CommandSugg
|
|||
if (context.getInputQueue().isEmpty()) {
|
||||
input = "";
|
||||
} else {
|
||||
input = context.getInputQueue().peek();
|
||||
input = String.join(" ", context.getInputQueue());
|
||||
}
|
||||
final List<String> suggestions = new LinkedList<>();
|
||||
final List<String> suggestions = new ArrayList<>(strings.size());
|
||||
for (final String suggestion : strings) {
|
||||
if (suggestion.startsWith(input)) {
|
||||
suggestions.add(suggestion);
|
||||
final @Nullable String filtered = this.filter.filter(context, suggestion, input);
|
||||
if (filtered != null) {
|
||||
suggestions.add(filtered);
|
||||
}
|
||||
}
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter function that tests (and potentially changes) each suggestion against the input and context.
|
||||
*
|
||||
* @param <C> sender type
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
@FunctionalInterface
|
||||
public interface Filter<C> {
|
||||
|
||||
/**
|
||||
* Filters a potential suggestion against the input and context.
|
||||
*
|
||||
* @param context context
|
||||
* @param suggestion potential suggestion
|
||||
* @param input remaining unconsumed input
|
||||
* @return possibly modified suggestion or null to deny
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
@Nullable String filter(
|
||||
@NonNull CommandPreprocessingContext<C> context,
|
||||
@NonNull String suggestion,
|
||||
@NonNull String input
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a new {@link Filter} which tests this filter, and if the result
|
||||
* is non-null, then filters with {@code and}.
|
||||
*
|
||||
* @param and next filter
|
||||
* @return combined filter
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
default @NonNull Filter<C> and(final @NonNull Filter<C> and) {
|
||||
return (ctx, suggestion, input) -> {
|
||||
final @Nullable String filtered = this.filter(ctx, suggestion, input);
|
||||
if (filtered == null) {
|
||||
return null;
|
||||
}
|
||||
return and.filter(ctx, filtered, input);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Filter} that tests this filter, and then
|
||||
* uses {@link #trimBeforeLastSpace()} if the result is non-null.
|
||||
*
|
||||
* @return combined filter
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
default Filter<C> andTrimBeforeLastSpace() {
|
||||
return this.and(trimBeforeLastSpace());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a filter using {@link String#startsWith(String)} that can optionally ignore case.
|
||||
*
|
||||
* @param ignoreCase whether to ignore case
|
||||
* @param <C> sender type
|
||||
* @return new filter
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
static <C> @NonNull Simple<C> startsWith(final boolean ignoreCase) {
|
||||
final BiPredicate<String, String> test = ignoreCase
|
||||
? (suggestion, input) -> suggestion.toLowerCase(Locale.ROOT).startsWith(input.toLowerCase(Locale.ROOT))
|
||||
: String::startsWith;
|
||||
return Simple.contextFree(test);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a filter using {@link String#contains(CharSequence)} that can optionally ignore case.
|
||||
*
|
||||
* @param ignoreCase whether to ignore case
|
||||
* @param <C> sender type
|
||||
* @return new filter
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
static <C> @NonNull Simple<C> contains(final boolean ignoreCase) {
|
||||
final BiPredicate<String, String> test = ignoreCase
|
||||
? (suggestion, input) -> suggestion.toLowerCase(Locale.ROOT).contains(input.toLowerCase(Locale.ROOT))
|
||||
: String::contains;
|
||||
return Simple.contextFree(test);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a filter which does extra processing when the input contains spaces.
|
||||
*
|
||||
* <p>Will return the portion of the suggestion which is after the last space in
|
||||
* the input.</p>
|
||||
*
|
||||
* @param <C> sender type
|
||||
* @return new filter
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
static <C> @NonNull Filter<C> trimBeforeLastSpace() {
|
||||
return (context, suggestion, input) -> {
|
||||
final int lastSpace = input.lastIndexOf(' ');
|
||||
// No spaces in input, don't do anything
|
||||
if (lastSpace == -1) {
|
||||
return suggestion;
|
||||
}
|
||||
|
||||
// Always use case-insensitive here. If case-sensitive filtering is desired it should
|
||||
// be done in another filter which this is appended to using #and/#andTrimBeforeLastSpace.
|
||||
if (suggestion.toLowerCase(Locale.ROOT).startsWith(input.toLowerCase(Locale.ROOT).substring(0, lastSpace))) {
|
||||
return suggestion.substring(lastSpace + 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new context-free {@link Filter}.
|
||||
*
|
||||
* @param function function
|
||||
* @param <C> sender type
|
||||
* @return filter
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
static <C> @NonNull Filter<C> contextFree(final @NonNull BiFunction<String, String, @Nullable String> function) {
|
||||
return (ctx, suggestion, input) -> function.apply(suggestion, input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Simple}. This is a convenience method to allow
|
||||
* for more easily implementing {@link Simple} using a lambda without
|
||||
* casting, for methods which accept {@link Filter}.
|
||||
*
|
||||
* @param filter filter lambda
|
||||
* @param <C> sender type
|
||||
* @return new simple filter
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
static <C> @NonNull Simple<C> simple(final Simple<C> filter) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple version of {@link Filter} which doesn't modify suggestions.
|
||||
*
|
||||
* <p>Returns boolean instead of nullable String.</p>
|
||||
*
|
||||
* @param <C> sender type
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
@FunctionalInterface
|
||||
interface Simple<C> extends Filter<C> {
|
||||
|
||||
/**
|
||||
* Tests a suggestion against the context and input.
|
||||
*
|
||||
* @param context context
|
||||
* @param suggestion potential suggestion
|
||||
* @param input remaining unconsumed input
|
||||
* @return whether to accept the suggestion
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
boolean test(
|
||||
@NonNull CommandPreprocessingContext<C> context,
|
||||
@NonNull String suggestion,
|
||||
@NonNull String input
|
||||
);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("FunctionalInterfaceMethodChanged")
|
||||
default @Nullable String filter(
|
||||
@NonNull CommandPreprocessingContext<C> context,
|
||||
@NonNull String suggestion,
|
||||
@NonNull String input
|
||||
) {
|
||||
if (this.test(context, suggestion, input)) {
|
||||
return suggestion;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new context-free {@link Simple}.
|
||||
*
|
||||
* @param test predicate
|
||||
* @param <C> sender type
|
||||
* @return simple filter
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||
static <C> @NonNull Simple<C> contextFree(final @NonNull BiPredicate<String, String> test) {
|
||||
return (ctx, suggestion, input) -> test.test(suggestion, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@ public class CommandSuggestionsTest {
|
|||
assertThat(suggestions3).containsExactly("--flag", "--flag2");
|
||||
assertThat(suggestions4).containsExactly("--flag", "--flag2");
|
||||
assertThat(suggestions5).containsExactly("-f");
|
||||
assertThat(suggestions6).containsExactly("hello");
|
||||
assertThat(suggestions6).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue