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
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import cloud.commandframework.bukkit.parsers.selector.MultiplePlayerSelectorArgu
|
|||
import cloud.commandframework.bukkit.parsers.selector.SingleEntitySelectorArgument;
|
||||
import cloud.commandframework.bukkit.parsers.selector.SinglePlayerSelectorArgument;
|
||||
import cloud.commandframework.execution.CommandExecutionCoordinator;
|
||||
import cloud.commandframework.execution.FilteringCommandSuggestionProcessor;
|
||||
import cloud.commandframework.tasks.TaskFactory;
|
||||
import cloud.commandframework.tasks.TaskRecipe;
|
||||
import io.leangen.geantyref.TypeToken;
|
||||
|
|
@ -134,6 +135,10 @@ public class BukkitCommandManager<C> extends CommandManager<C> implements Brigad
|
|||
final BukkitSynchronizer bukkitSynchronizer = new BukkitSynchronizer(owningPlugin);
|
||||
this.taskFactory = new TaskFactory(bukkitSynchronizer);
|
||||
|
||||
this.commandSuggestionProcessor(new FilteringCommandSuggestionProcessor<>(
|
||||
FilteringCommandSuggestionProcessor.Filter.<C>startsWith(true).andTrimBeforeLastSpace()
|
||||
));
|
||||
|
||||
/* Register capabilities */
|
||||
CloudBukkitCapabilities.CAPABLE.forEach(this::registerCapability);
|
||||
this.registerCapability(CloudCapability.StandardCapabilities.ROOT_COMMAND_DELETION);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import cloud.commandframework.bungee.arguments.PlayerArgument;
|
|||
import cloud.commandframework.bungee.arguments.ServerArgument;
|
||||
import cloud.commandframework.captions.FactoryDelegatingCaptionRegistry;
|
||||
import cloud.commandframework.execution.CommandExecutionCoordinator;
|
||||
import cloud.commandframework.execution.FilteringCommandSuggestionProcessor;
|
||||
import cloud.commandframework.meta.SimpleCommandMeta;
|
||||
import io.leangen.geantyref.TypeToken;
|
||||
import java.util.function.Function;
|
||||
|
|
@ -76,6 +77,10 @@ public class BungeeCommandManager<C> extends CommandManager<C> {
|
|||
this.commandSenderMapper = commandSenderMapper;
|
||||
this.backwardsCommandSenderMapper = backwardsCommandSenderMapper;
|
||||
|
||||
this.commandSuggestionProcessor(new FilteringCommandSuggestionProcessor<>(
|
||||
FilteringCommandSuggestionProcessor.Filter.<C>startsWith(true).andTrimBeforeLastSpace()
|
||||
));
|
||||
|
||||
/* Register Bungee Preprocessor */
|
||||
this.registerCommandPreProcessor(new BungeeCommandPreprocessor<>(this));
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ package cloud.commandframework.cloudburst;
|
|||
import cloud.commandframework.CommandManager;
|
||||
import cloud.commandframework.CommandTree;
|
||||
import cloud.commandframework.execution.CommandExecutionCoordinator;
|
||||
import cloud.commandframework.execution.FilteringCommandSuggestionProcessor;
|
||||
import cloud.commandframework.meta.CommandMeta;
|
||||
import cloud.commandframework.meta.SimpleCommandMeta;
|
||||
import java.util.function.Function;
|
||||
|
|
@ -69,6 +70,9 @@ public class CloudburstCommandManager<C> extends CommandManager<C> {
|
|||
this.commandSenderMapper = commandSenderMapper;
|
||||
this.backwardsCommandSenderMapper = backwardsCommandSenderMapper;
|
||||
this.owningPlugin = owningPlugin;
|
||||
this.commandSuggestionProcessor(new FilteringCommandSuggestionProcessor<>(
|
||||
FilteringCommandSuggestionProcessor.Filter.<C>startsWith(true).andTrimBeforeLastSpace()
|
||||
));
|
||||
|
||||
// Prevent commands from being registered when the server would reject them anyways
|
||||
this.owningPlugin.getServer().getPluginManager().registerEvent(
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
|
|||
import cloud.commandframework.context.CommandContext;
|
||||
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
|
||||
import cloud.commandframework.execution.CommandExecutionCoordinator;
|
||||
import cloud.commandframework.execution.FilteringCommandSuggestionProcessor;
|
||||
import cloud.commandframework.fabric.argument.FabricArgumentParsers;
|
||||
import cloud.commandframework.fabric.argument.RegistryEntryArgument;
|
||||
import cloud.commandframework.fabric.argument.TeamArgument;
|
||||
|
|
@ -154,6 +155,9 @@ public abstract class FabricCommandManager<C, S extends SharedSuggestionProvider
|
|||
this.registerNativeBrigadierMappings(this.brigadierManager);
|
||||
this.captionRegistry(new FabricCaptionRegistry<>());
|
||||
this.registerCommandPreProcessor(new FabricCommandPreprocessor<>(this));
|
||||
this.commandSuggestionProcessor(new FilteringCommandSuggestionProcessor<>(
|
||||
FilteringCommandSuggestionProcessor.Filter.<C>startsWith(true).andTrimBeforeLastSpace()
|
||||
));
|
||||
|
||||
((FabricCommandRegistrationHandler<C, S>) this.commandRegistrationHandler()).initialize(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import cloud.commandframework.CommandManager;
|
|||
import cloud.commandframework.CommandTree;
|
||||
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
|
||||
import cloud.commandframework.execution.CommandExecutionCoordinator;
|
||||
import cloud.commandframework.execution.FilteringCommandSuggestionProcessor;
|
||||
import cloud.commandframework.meta.CommandMeta;
|
||||
import cloud.commandframework.meta.SimpleCommandMeta;
|
||||
import java.util.function.Function;
|
||||
|
|
@ -78,6 +79,9 @@ public class SpongeCommandManager<C> extends CommandManager<C> {
|
|||
this.owningPlugin = requireNonNull(container, "container");
|
||||
this.forwardMapper = requireNonNull(forwardMapper, "forwardMapper");
|
||||
this.reverseMapper = requireNonNull(reverseMapper, "reverseMapper");
|
||||
this.commandSuggestionProcessor(new FilteringCommandSuggestionProcessor<>(
|
||||
FilteringCommandSuggestionProcessor.Filter.<C>startsWith(true).andTrimBeforeLastSpace()
|
||||
));
|
||||
((SpongePluginRegistrationHandler<C>) this.commandRegistrationHandler()).initialize(this);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import cloud.commandframework.brigadier.BrigadierManagerHolder;
|
|||
import cloud.commandframework.brigadier.CloudBrigadierManager;
|
||||
import cloud.commandframework.captions.FactoryDelegatingCaptionRegistry;
|
||||
import cloud.commandframework.execution.CommandExecutionCoordinator;
|
||||
import cloud.commandframework.execution.FilteringCommandSuggestionProcessor;
|
||||
import cloud.commandframework.meta.CommandMeta;
|
||||
import cloud.commandframework.meta.SimpleCommandMeta;
|
||||
import cloud.commandframework.velocity.arguments.PlayerArgument;
|
||||
|
|
@ -114,6 +115,10 @@ public class VelocityCommandManager<C> extends CommandManager<C> implements Brig
|
|||
this.commandSenderMapper = commandSenderMapper;
|
||||
this.backwardsCommandSenderMapper = backwardsCommandSenderMapper;
|
||||
|
||||
this.commandSuggestionProcessor(new FilteringCommandSuggestionProcessor<>(
|
||||
FilteringCommandSuggestionProcessor.Filter.<C>startsWith(true).andTrimBeforeLastSpace()
|
||||
));
|
||||
|
||||
/* Register Velocity Preprocessor */
|
||||
this.registerCommandPreProcessor(new VelocityCommandPreprocessor<>(this));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue