Add Support for Async Tab Completions for Paper Servers

This adds the ability for plugins to define completion handlers as async safe (not on games main thread)

When they are defined async safe, and ran on a Paper 1.12.2+ server, with a Paper ACF manager,
completions will be handled mostly async, letting you safely do heavier operations in tab completions.
This commit is contained in:
Aikar
2017-11-26 23:21:15 -05:00
parent 229192f99c
commit fbed6f2be3
31 changed files with 278 additions and 103 deletions
+3
View File
@@ -7,6 +7,9 @@
</value>
</option>
<option name="LINE_SEPARATOR" value="&#10;" />
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="15" />
</JavaCodeStyleSettings>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
@@ -43,12 +43,12 @@ import java.util.stream.Stream;
public class BukkitCommandCompletions extends CommandCompletions<BukkitCommandCompletionContext> {
public BukkitCommandCompletions(BukkitCommandManager manager) {
super(manager);
registerCompletion("mobs", c -> {
registerAsyncCompletion("mobs", c -> {
final Stream<String> normal = Stream.of(EntityType.values())
.map(entityType -> ACFUtil.simplifyString(entityType.getName()));
return normal.collect(Collectors.toList());
});
registerCompletion("chatcolors", c -> {
registerAsyncCompletion("chatcolors", c -> {
Stream<ChatColor> colors = Stream.of(ChatColor.values());
if (c.hasConfig("colorsonly")) {
colors = colors.filter(color -> color.ordinal() <= 0xF);
@@ -63,7 +63,7 @@ public class BukkitCommandCompletions extends CommandCompletions<BukkitCommandCo
return colors.map(color -> ACFUtil.simplifyString(color.name())).collect(Collectors.toList());
});
registerCompletion("dyecolors", c -> ACFUtil.enumNames(DyeColor.values()));
registerAsyncCompletion("dyecolors", c -> ACFUtil.enumNames(DyeColor.values()));
registerCompletion("worlds", c -> (
Bukkit.getWorlds().stream().map(World::getName).collect(Collectors.toList())
));
@@ -23,7 +23,6 @@
package co.aikar.commands;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -24,7 +24,6 @@
package co.aikar.commands;
import co.aikar.timings.lib.MCTiming;
import co.aikar.timings.lib.TimingManager;
import java.lang.reflect.Method;
@@ -27,14 +27,11 @@ import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Syntax;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import jdk.nashorn.internal.ir.ReturnNode;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class BukkitRootCommand extends Command implements RootCommand {
@@ -58,7 +55,7 @@ public class BukkitRootCommand extends Command implements RootCommand {
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
return tabComplete(manager.getCommandIssuer(sender), alias, args);
return getTabCompletions(manager.getCommandIssuer(sender), alias, args);
}
@Override
@@ -67,14 +64,6 @@ public class BukkitRootCommand extends Command implements RootCommand {
return true;
}
private List<String> tabComplete(CommandIssuer sender, String alias, String[] args) throws IllegalArgumentException {
Set<String> completions = new HashSet<>();
this.children.forEach(child -> completions.addAll(child.tabComplete(sender, alias, args)));
return new ArrayList<>(completions);
}
public void addChild(BaseCommand command) {
if (this.defCommand == null || !command.subCommands.get("__default").isEmpty()) {
this.defCommand = command;
@@ -95,6 +84,11 @@ public class BukkitRootCommand extends Command implements RootCommand {
return this.subCommands;
}
@Override
public List<BaseCommand> getChildren() {
return children;
}
@Override
public BaseCommand getDefCommand(){
return defCommand;
@@ -39,7 +39,7 @@ public class BungeeCommandCompletions extends CommandCompletions<BungeeCommandCo
public BungeeCommandCompletions(CommandManager manager) {
super(manager);
registerCompletion("chatcolors", c -> {
registerAsyncCompletion("chatcolors", c -> {
Stream<ChatColor> colors = Stream.of(ChatColor.values());
if (c.hasConfig("colorsonly")) {
colors = colors.filter(color -> color.ordinal() <= 0xF);
@@ -24,7 +24,6 @@
package co.aikar.commands;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.Objects;
@@ -30,9 +30,9 @@ import net.md_5.bungee.api.plugin.Plugin;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -29,10 +29,8 @@ import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
public class BungeeRootCommand extends Command implements RootCommand, TabExecutor {
@@ -73,6 +71,11 @@ public class BungeeRootCommand extends Command implements RootCommand, TabExecut
return subCommands;
}
@Override
public List<BaseCommand> getChildren() {
return children;
}
@Override
public void execute(CommandSender sender, String[] args) {
execute(manager.getCommandIssuer(sender), getName(), args);
@@ -80,13 +83,7 @@ public class BungeeRootCommand extends Command implements RootCommand, TabExecut
@Override
public Iterable<String> onTabComplete(CommandSender commandSender, String[] strings) {
return onTabComplete(manager.getCommandIssuer(commandSender), getName(), strings);
}
private List<String> onTabComplete(CommandIssuer sender, String alias, String[] args) throws IllegalArgumentException {
Set<String> completions = new HashSet<>();
this.children.forEach(child -> completions.addAll(child.tabComplete(sender, alias, args)));
return new ArrayList<>(completions);
return getTabCompletions(manager.getCommandIssuer(commandSender), getName(), strings);
}
@Override
@@ -23,7 +23,6 @@
package co.aikar.commands;
import com.google.common.collect.Maps;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
@@ -25,7 +25,6 @@ package co.aikar.commands;
import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.math.BigDecimal;
@@ -23,16 +23,37 @@
package co.aikar.commands;
import co.aikar.commands.annotation.*;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import co.aikar.commands.annotation.HelpCommand;
import co.aikar.commands.annotation.PreCommand;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.UnknownHandler;
import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil;
import com.google.common.collect.*;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -307,7 +328,7 @@ public abstract class BaseCommand {
public void execute(CommandIssuer issuer, String commandLabel, String[] args) {
commandLabel = commandLabel.toLowerCase();
try {
CommandOperationContext commandContext = preCommandOperation(issuer, commandLabel, args);
CommandOperationContext commandContext = preCommandOperation(issuer, commandLabel, args, false);
if (args.length > 0) {
CommandSearch cmd = findSubCommand(args);
@@ -319,7 +340,6 @@ public abstract class BaseCommand {
}
}
if (subCommands.get(DEFAULT) != null && args.length == 0) {
executeSubcommand(commandContext, DEFAULT, issuer, args);
} else if (subCommands.get(UNKNOWN) != null) {
@@ -335,6 +355,11 @@ public abstract class BaseCommand {
}
}
RegisteredCommand<?> getRegisteredCommand(String[] args) {
final CommandSearch cmd = findSubCommand(args);
return cmd != null ? cmd.cmd : null;
}
private void postCommandOperation() {
CommandManager.commandOperationContext.get().pop();
execSubcommand = null;
@@ -342,9 +367,9 @@ public abstract class BaseCommand {
origArgs = new String[]{};
}
private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args) {
private CommandOperationContext preCommandOperation(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) {
Stack<CommandOperationContext> contexts = CommandManager.commandOperationContext.get();
CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args);
CommandOperationContext context = this.manager.createCommandOperationContext(this, issuer, commandLabel, args, isAsync);
contexts.push(context);
lastCommandOperationContext = context;
execSubcommand = null;
@@ -417,37 +442,28 @@ public abstract class BaseCommand {
return true;
}
public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args)
public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args) {
return tabComplete(issuer, commandLabel, args, false);
}
public List<String> tabComplete(CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync)
throws IllegalArgumentException {
commandLabel = commandLabel.toLowerCase();
if (args.length == 0) {
args = new String[]{""};
}
try {
CommandOperationContext commandOperationContext = preCommandOperation(issuer, commandLabel, args);
CommandOperationContext commandOperationContext = preCommandOperation(issuer, commandLabel, args, isAsync);
final CommandSearch search = findSubCommand(args, true);
String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase();
final List<String> cmds = new ArrayList<>();
if (search != null) {
cmds.addAll(completeCommand(commandOperationContext, issuer, search.cmd, Arrays.copyOfRange(args, search.argIndex, args.length), commandLabel));
cmds.addAll(completeCommand(issuer, search.cmd, Arrays.copyOfRange(args, search.argIndex, args.length), commandLabel, isAsync));
} else if (subCommands.get(UNKNOWN).size() == 1) {
cmds.addAll(completeCommand(commandOperationContext, issuer, Iterables.getOnlyElement(subCommands.get(UNKNOWN)), args, commandLabel));
}
for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) {
final String key = entry.getKey();
if (key.startsWith(argString) && !UNKNOWN.equals(key) && !DEFAULT.equals(key)) {
final RegisteredCommand value = entry.getValue();
if (!value.hasPermission(issuer)) {
continue;
}
String prefCommand = value.prefSubCommand;
final String[] psplit = ACFPatterns.SPACE.split(prefCommand);
cmds.add(psplit[args.length - 1]);
}
cmds.addAll(completeCommand(issuer, Iterables.getOnlyElement(subCommands.get(UNKNOWN)), args, commandLabel, isAsync));
}
return filterTabComplete(args[args.length - 1], cmds);
@@ -456,7 +472,25 @@ public abstract class BaseCommand {
}
}
private List<String> completeCommand(CommandOperationContext commandOperationContext, CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel) {
List<String> getCommandsForCompletion(CommandIssuer issuer, String[] args) {
final Set<String> cmds = new HashSet<>();
String argString = ApacheCommonsLangUtil.join(args, " ").toLowerCase();
for (Map.Entry<String, RegisteredCommand> entry : subCommands.entries()) {
final String key = entry.getKey();
if (key.startsWith(argString) && !UNKNOWN.equals(key) && !DEFAULT.equals(key)) {
final RegisteredCommand value = entry.getValue();
if (!value.hasPermission(issuer)) {
continue;
}
String[] split = ACFPatterns.SPACE.split(value.prefSubCommand);
cmds.add(split[args.length - 1]);
}
}
return new ArrayList<>(cmds);
}
private List<String> completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) {
if (!cmd.hasPermission(issuer) || args.length > cmd.requiredResolvers + cmd.optionalResolvers || args.length == 0
|| cmd.complete == null) {
return ImmutableList.of();
@@ -464,7 +498,7 @@ public abstract class BaseCommand {
String[] completions = ACFPatterns.SPACE.split(cmd.complete);
List<String> cmds = manager.getCommandCompletions().of(commandOperationContext, cmd, issuer, completions, args);
List<String> cmds = manager.getCommandCompletions().of(cmd, issuer, completions, args, isAsync);
return filterTabComplete(args[args.length-1], cmds);
}
@@ -124,4 +124,8 @@ public class CommandCompletionContext {
public String getConfig() {
return config;
}
public boolean isAsync() {
return CommandManager.getCurrentCommandOperationContext().isAsync();
}
}
@@ -42,8 +42,8 @@ public class CommandCompletions <C extends CommandCompletionContext> {
public CommandCompletions(CommandManager manager) {
this.manager = manager;
registerCompletion("nothing", c -> ImmutableList.of());
registerCompletion("range", (c) -> {
registerAsyncCompletion("nothing", c -> ImmutableList.of());
registerAsyncCompletion("range", (c) -> {
String config = c.getConfig();
if (config == null) {
return ImmutableList.of();
@@ -60,15 +60,19 @@ public class CommandCompletions <C extends CommandCompletionContext> {
}
return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
});
registerCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
}
public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
return this.completionMap.put("@" + id.toLowerCase(), handler);
}
public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
return this.completionMap.put("@" + id.toLowerCase(), handler);
}
@NotNull
List<String> of(CommandOperationContext commandOperationContext, RegisteredCommand command, CommandIssuer sender, String[] completionInfo, String[] args) {
List<String> of(RegisteredCommand command, CommandIssuer sender, String[] completionInfo, String[] args, boolean isAsync) {
final int argIndex = args.length - 1;
String input = args[argIndex];
@@ -77,11 +81,10 @@ public class CommandCompletions <C extends CommandCompletionContext> {
return ImmutableList.of(input);
}
return getCompletionValues(command, sender, completion, args);
return getCompletionValues(command, sender, completion, args, isAsync);
}
@NotNull
List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args) {
List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
completion = manager.getCommandReplacements().replace(completion);
List<String> allCompletions = Lists.newArrayList();
@@ -91,6 +94,10 @@ public class CommandCompletions <C extends CommandCompletionContext> {
String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
if (handler != null) {
if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
ACFUtil.sneaky(new SyncCompletionRequired());
return null;
}
String config = complete.length == 1 ? null : complete[1];
CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
@@ -123,5 +130,7 @@ public class CommandCompletions <C extends CommandCompletionContext> {
public interface CommandCompletionHandler <C extends CommandCompletionContext> {
Collection<String> getCompletions(C context) throws InvalidCommandArgument;
}
public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends CommandCompletionHandler <C> {}
public static class SyncCompletionRequired extends Exception {}
}
@@ -31,6 +31,7 @@ import co.aikar.commands.contexts.IssuerAwareContextResolver;
import co.aikar.commands.contexts.IssuerOnlyContextResolver;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
import java.util.List;
@@ -27,7 +27,12 @@ import co.aikar.locales.MessageKeyProvider;
import com.google.common.collect.SetMultimap;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
@SuppressWarnings("WeakerAccess")
@@ -132,7 +132,7 @@ public abstract class CommandManager <I, AI extends CommandIssuer, FT, F extends
/** @deprecated Unstable API */ @Deprecated @UnstableAPI
public CommandHelp generateCommandHelp(CommandIssuer issuer, @NotNull String command) {
verifyUnstableAPI("help");
return generateCommandHelp(issuer, obtainRootCommand(ACFPatterns.SPACE.split(command, 2)[0]));
return generateCommandHelp(issuer, obtainRootCommand(command));
}
/** @deprecated Unstable API */ @Deprecated @UnstableAPI
@@ -230,8 +230,16 @@ public abstract class CommandManager <I, AI extends CommandIssuer, FT, F extends
return true;
}
public synchronized RootCommand obtainRootCommand(String cmd) {
return rootCommands.computeIfAbsent(cmd.toLowerCase(), this::createRootCommand);
BaseCommand getBaseCommand(String commandLabel, @NotNull String[] args) {
RootCommand rootCommand = obtainRootCommand(commandLabel);
if (rootCommand == null) {
return null;
}
return rootCommand.getBaseCommand(args);
}
public synchronized RootCommand obtainRootCommand(@NotNull String cmd) {
return rootCommands.computeIfAbsent(ACFPatterns.SPACE.split(cmd.toLowerCase(), 2)[0], this::createRootCommand);
}
public RegisteredCommand createRegisteredCommand(BaseCommand command, String cmdName, Method method, String prefSubCommand) {
@@ -311,14 +319,14 @@ public abstract class CommandManager <I, AI extends CommandIssuer, FT, F extends
return getLocales().getDefaultLocale();
}
public CommandOperationContext createCommandOperationContext(BaseCommand command, CommandIssuer issuer, String commandLabel, String[] args) {
CommandOperationContext createCommandOperationContext(BaseCommand command, CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) {
return new CommandOperationContext(
this,
issuer,
command,
commandLabel,
args
args,
isAsync
);
}
@@ -35,14 +35,16 @@ public class CommandOperationContext {
private final BaseCommand command;
private final String commandLabel;
private final String[] args;
private final boolean isAsync;
private RegisteredCommand registeredCommand;
CommandOperationContext(CommandManager manager, CommandIssuer issuer, BaseCommand command, String commandLabel, String[] args) {
CommandOperationContext(CommandManager manager, CommandIssuer issuer, BaseCommand command, String commandLabel, String[] args, boolean isAsync) {
this.manager = manager;
this.issuer = issuer;
this.command = command;
this.commandLabel = commandLabel;
this.args = args;
this.isAsync = isAsync;
}
public CommandManager getCommandManager() {
@@ -65,6 +67,10 @@ public class CommandOperationContext {
return args;
}
public boolean isAsync() {
return isAsync;
}
public void setRegisteredCommand(RegisteredCommand registeredCommand) {
this.registeredCommand = registeredCommand;
}
@@ -23,7 +23,10 @@
package co.aikar.commands;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
/**
@@ -23,7 +23,14 @@
package co.aikar.commands;
import co.aikar.commands.annotation.*;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Syntax;
import co.aikar.commands.annotation.Values;
import co.aikar.commands.contexts.ContextResolver;
import co.aikar.commands.contexts.IssuerAwareContextResolver;
import co.aikar.commands.contexts.IssuerOnlyContextResolver;
@@ -188,6 +195,7 @@ public class RegisteredCommand <R extends CommandExecutionContext<? extends Comm
String[] origArgs = args.toArray(new String[args.size()]);
Map<String, Object> passedArgs = Maps.newLinkedHashMap();
int remainingRequired = requiredResolvers;
CommandOperationContext opContext = CommandManager.getCurrentCommandOperationContext();
for (int i = 0; i < parameters.length && i < argLimit; i++) {
boolean isLast = i == parameters.length - 1;
boolean allowOptional = remainingRequired == 0;
@@ -222,7 +230,7 @@ public class RegisteredCommand <R extends CommandExecutionContext<? extends Comm
final String[] split = ACFPatterns.PIPE.split(scope.manager.getCommandReplacements().replace(values.value()));
Set<String> possible = Sets.newHashSet();
for (String s : split) {
List<String> check = this.manager.getCommandCompletions().getCompletionValues(this, sender, s, origArgs);
List<String> check = this.manager.getCommandCompletions().getCompletionValues(this, sender, s, origArgs, opContext.isAsync());
if (!check.isEmpty()) {
possible.addAll(check.stream().map(String::toLowerCase).collect(Collectors.toList()));
} else {
@@ -26,6 +26,8 @@ package co.aikar.commands;
import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil;
import com.google.common.collect.SetMultimap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -34,6 +36,7 @@ interface RootCommand {
CommandManager getManager();
SetMultimap<String, RegisteredCommand> getSubCommands();
List<BaseCommand> getChildren();
String getCommandName();
default void addChildShared(List<BaseCommand> children, SetMultimap<String, RegisteredCommand> subCommands, BaseCommand command) {
@@ -59,6 +62,13 @@ interface RootCommand {
}
default BaseCommand execute(CommandIssuer sender, String commandLabel, String[] args) {
BaseCommand command = getBaseCommand(args);
command.execute(sender, commandLabel, args);
return command;
}
default BaseCommand getBaseCommand(String[] args) {
BaseCommand command = getDefCommand();
for (int i = args.length; i >= 0; i--) {
String checkSub = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase();
@@ -68,11 +78,19 @@ interface RootCommand {
break;
}
}
command.execute(sender, commandLabel, args);
return command;
}
default List<String> getTabCompletions(CommandIssuer sender, String alias, String[] args) throws IllegalArgumentException {
Set<String> completions = new HashSet<>();
getChildren().forEach(child -> {
completions.addAll(child.tabComplete(sender, alias, args));
completions.addAll(child.getCommandsForCompletion(sender, args));
});
return new ArrayList<>(completions);
}
default RegisteredCommand getDefaultRegisteredCommand() {
BaseCommand defCommand = this.getDefCommand();
if (defCommand != null) {
@@ -60,7 +60,7 @@ public final class ACFExample extends JavaPlugin {
SomeObject.getContextResolver());
// 4: Register Command Completions - this will be accessible with @CommandCompletion("@test")
commandManager.getCommandCompletions().registerCompletion("test", c -> (
commandManager.getCommandCompletions().registerAsyncCompletion("test", c -> (
Lists.newArrayList("foo", "bar", "baz")
));
+1 -1
View File
@@ -49,7 +49,7 @@
<dependency>
<groupId>com.destroystokyo.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.12-R0.1-SNAPSHOT</version>
<version>1.12.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -0,0 +1,93 @@
/*
* Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package co.aikar.commands;
import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.TabCompleteEvent;
import java.util.Arrays;
import java.util.List;
class PaperAsyncTabCompleteHandler implements Listener {
private final PaperCommandManager manager;
PaperAsyncTabCompleteHandler(PaperCommandManager manager) {
this.manager = manager;
manager.log(LogLevel.INFO, "Enabled Asynchronous Tab Completion Support!");
}
@EventHandler(ignoreCancelled = true)
public void onAsyncTabComplete(AsyncTabCompleteEvent event) {
String buffer = event.getBuffer();
if (!event.isCommand() && !buffer.startsWith("/")) {
return;
}
try {
//noinspection ConstantConditions,ConstantIfStatement
if (false) throw new CommandCompletions.SyncCompletionRequired(); // fake compiler due to SneakyThrows
String[] args = ACFPatterns.SPACE.split(buffer, -1);
String commandLabel = args[0];
if (commandLabel.startsWith("/")) {
commandLabel = commandLabel.substring(1);
}
args = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[]{""};
BaseCommand cmd = this.manager.getBaseCommand(commandLabel, args);
if (cmd == null) {
return;
}
BukkitCommandIssuer issuer = this.manager.getCommandIssuer(event.getSender());
List<String> results = cmd.tabComplete(issuer, commandLabel, args, true);
event.setCompletions(results);
event.setHandled(true);
} catch (Exception ignored) {
}
}
@EventHandler(ignoreCancelled = true)
public void onTabComplete(TabCompleteEvent event) {
String buffer = event.getBuffer();
if (!event.isCommand() && !buffer.startsWith("/")) {
return;
}
String[] args = ACFPatterns.SPACE.split(buffer, -1);
String commandLabel = args[0];
if (commandLabel.startsWith("/")) {
commandLabel = commandLabel.substring(1);
}
args = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[]{""};
BaseCommand cmd = this.manager.getBaseCommand(commandLabel, args);
if (cmd != null) {
BukkitCommandIssuer issuer = this.manager.getCommandIssuer(event.getSender());
List<String> commandsForCompletion = cmd.getCommandsForCompletion(issuer, args);
event.getCompletions().addAll(commandsForCompletion);
}
}
}
@@ -23,7 +23,6 @@
package co.aikar.commands;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
@SuppressWarnings("WeakerAccess")
@@ -32,6 +31,12 @@ public class PaperCommandManager extends BukkitCommandManager {
// If we get anything Paper specific
public PaperCommandManager(Plugin plugin) {
super(plugin);
try {
Class.forName("com.destroystokyo.paper.event.server.AsyncTabCompleteEvent");
plugin.getServer().getPluginManager().registerEvents(new PaperAsyncTabCompleteHandler(this), plugin);
} catch (ClassNotFoundException ignored) {
// Ignored
}
}
@Override
@@ -1,7 +1,6 @@
package co.aikar.commands;
import com.google.common.collect.Iterables;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.entity.living.player.Player;
@@ -31,11 +31,9 @@ import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.text.format.TextColor;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.text.format.TextStyle;
import org.spongepowered.api.world.World;
import javax.swing.plaf.ActionMapUIResource;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
@@ -25,8 +25,6 @@ package co.aikar.commands;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.text.serializer.TextSerializers;
import java.util.Objects;
@@ -172,13 +172,14 @@ public class SpongeCommandManager extends CommandManager<CommandSource, SpongeCo
}
@Override
public CommandOperationContext createCommandOperationContext(BaseCommand command, CommandIssuer issuer, String commandLabel, String[] args) {
CommandOperationContext createCommandOperationContext(BaseCommand command, CommandIssuer issuer, String commandLabel, String[] args, boolean isAsync) {
return new SpongeCommandOperationContext(
this,
issuer,
command,
commandLabel,
args
args,
isAsync
);
}
}
@@ -4,8 +4,8 @@ import org.spongepowered.api.command.CommandResult;
public class SpongeCommandOperationContext extends CommandOperationContext {
private CommandResult result = CommandResult.success();
SpongeCommandOperationContext(CommandManager manager, CommandIssuer issuer, BaseCommand command, String commandLabel, String[] args) {
super(manager, issuer, command, commandLabel, args);
SpongeCommandOperationContext(CommandManager manager, CommandIssuer issuer, BaseCommand command, String commandLabel, String[] args, boolean isAsync) {
super(manager, issuer, command, commandLabel, args, isAsync);
}
public CommandResult getResult() {
@@ -34,13 +34,10 @@ import org.spongepowered.api.text.Text;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
public class SpongeRootCommand implements CommandCallable, RootCommand {
@@ -70,7 +67,7 @@ public class SpongeRootCommand implements CommandCallable, RootCommand {
@Override
public List<String> getSuggestions(@NotNull CommandSource source, @NotNull String arguments, @Nullable Location<World> location) throws CommandException {
String[] args = arguments.isEmpty() ? new String[0] : arguments.split(" ");
return tabComplete(manager.getCommandIssuer(source), this.name, args);
return getTabCompletions(manager.getCommandIssuer(source), this.name, args);
}
@Override
@@ -93,12 +90,6 @@ public class SpongeRootCommand implements CommandCallable, RootCommand {
return Text.of();
}
private List<String> tabComplete(CommandIssuer sender, String alias, String[] args) throws IllegalArgumentException {
Set<String> completions = new HashSet<>();
this.children.forEach(child -> completions.addAll(child.tabComplete(sender, alias, args)));
return new ArrayList<>(completions);
}
private CommandResult executeSponge(CommandIssuer sender, String commandLabel, String[] args) {
BaseCommand cmd = execute(sender, commandLabel, args);
return ((SpongeCommandOperationContext) cmd.lastCommandOperationContext).getResult();
@@ -125,4 +116,9 @@ public class SpongeRootCommand implements CommandCallable, RootCommand {
public SetMultimap<String, RegisteredCommand> getSubCommands() {
return subCommands;
}
@Override
public List<BaseCommand> getChildren() {
return children;
}
}