diff --git a/README.md b/README.md
index df71fbe..333d1be 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,6 @@ To use Blade, you simply have to include it as a dependency and shade it into yo
If you make any changes or improvements to the project, please consider making a pull request to merge your changes back into the upstream project.
This project is in its early stages, if you find any issues please open an issue.
-## TODO
-- [ ] Fix tab completion
-
## Using Blade
Maven
@@ -24,7 +21,7 @@ Maven
com.github.vaperion
blade
- 1.0.0
+ 1.1.0
compile
@@ -39,17 +36,18 @@ allprojects {
}
dependencies {
- implementation 'com.github.vaperion:blade:1.0.0'
+ implementation 'com.github.vaperion:blade:1.1.0'
}
```
### Example code
-Initialize Blade:
+Initializing Blade:
```java
import me.vaperion.blade.Blade;
import me.vaperion.blade.command.bindings.impl.BukkitBindings;
import me.vaperion.blade.command.container.impl.BukkitCommandContainer;
+import me.vaperion.blade.completer.impl.ProtocolLibTabCompleter;
import org.bukkit.plugin.java.JavaPlugin;
public class ExamplePlugin extends JavaPlugin {
@@ -66,6 +64,22 @@ public class ExamplePlugin extends JavaPlugin {
}
```
+Setting a custom tab completer:
+```java
+Blade.of()
+ ...
+ .tabCompleter(new ProtocolLibTabCompleter(this))
+ ...;
+```
+
+Registering a type provider without Bindings:
+```java
+Blade.of()
+ ...
+ .bind(Example.class, new BladeProvider() {...})
+ ...;
+```
+
Example commands:
```java
import me.vaperion.blade.command.annotation.*;
@@ -93,3 +107,59 @@ public class ExampleCommand {
}
}
```
+
+Example custom tab completer with Netty:
+```java
+import me.vaperion.blade.command.service.BladeCommandService;
+import me.vaperion.blade.completer.TabCompleter;
+import net.minecraft.server.v1_7_R4.PacketPlayInTabComplete;
+import net.minecraft.server.v1_7_R4.PacketPlayOutTabComplete;
+import net.minecraft.util.io.netty.channel.ChannelDuplexHandler;
+import net.minecraft.util.io.netty.channel.ChannelHandlerContext;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_7_R4.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class CustomTabCompleter implements TabCompleter, Listener {
+
+ private BladeCommandService commandService;
+
+ public CustomTabCompleter(JavaPlugin plugin) {
+ Bukkit.getServer().getPluginManager().registerEvents(this, plugin);
+ }
+
+ @Override
+ public void init(@NotNull BladeCommandService bladeCommandService) {
+ this.commandService = bladeCommandService;
+ }
+
+ @EventHandler
+ public void onJoin(PlayerJoinEvent event) {
+ Player player = event.getPlayer();
+ ((CraftPlayer) player).getHandle().playerConnection.networkManager.m.pipeline()
+ .addBefore("packet_handler", "blade_completer", new ChannelDuplexHandler() {
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ if (msg instanceof PacketPlayInTabComplete) {
+ String commandLine = ((PacketPlayInTabComplete) msg).c();
+ if (commandLine.startsWith("/")) {
+ commandLine = commandLine.substring(1);
+
+ List suggestions = commandService.getCommandCompleter().suggest(commandLine, () -> new BukkitSender(player), (cmd) -> hasPermission(player, cmd));
+ if (suggestions != null) {
+ ctx.writeAndFlush(new PacketPlayOutTabComplete(suggestions.toArray(new String[0])));
+ return;
+ }
+ }
+ }
+
+ super.channelRead(ctx, msg);
+ }
+ });
+ }
+}
+```
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 3236e6f..a313d3f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
me.vaperion
blade
- 1.0.0
+ 1.1.0
UTF-8
@@ -19,6 +19,10 @@
spigot-repo
https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+ dmulloy2-repo
+ https://repo.dmulloy2.net/repository/public/
+
@@ -28,36 +32,39 @@
1.18.16
provided
-
org.jetbrains
annotations
13.0
compile
-
org.spigotmc
spigot-api
1.16.5-R0.1-SNAPSHOT
provided
+
+ com.comphenix.protocol
+ ProtocolLib
+ 4.6.0
+ provided
+
+ clean install
org.apache.maven.plugins
maven-surefire-plugin
2.22.0
-
org.apache.maven.plugins
maven-dependency-plugin
3.1.1
-
org.apache.maven.plugins
maven-compiler-plugin
@@ -67,7 +74,6 @@
1.8
-
org.apache.maven.plugins
maven-shade-plugin
@@ -87,6 +93,4 @@
-
-
diff --git a/src/main/java/me/vaperion/blade/Blade.java b/src/main/java/me/vaperion/blade/Blade.java
index 9f0e295..26af96e 100644
--- a/src/main/java/me/vaperion/blade/Blade.java
+++ b/src/main/java/me/vaperion/blade/Blade.java
@@ -8,6 +8,7 @@
import me.vaperion.blade.command.bindings.Binding;
import me.vaperion.blade.command.container.ContainerCreator;
import me.vaperion.blade.command.service.BladeCommandService;
+import me.vaperion.blade.completer.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -22,6 +23,7 @@ public class Blade {
private final BladeCommandService commandService = new BladeCommandService();
private final String fallbackPrefix;
private final ContainerCreator> containerCreator;
+ private final TabCompleter tabCompleter;
@Singular("bind0") private final Map, Class extends ProviderAnnotation>>, BladeProvider>> customProviderMap;
@Singular private final List bindings;
@@ -56,10 +58,15 @@ public Blade build() {
if (blade.fallbackPrefix != null)
blade.commandService.setFallbackPrefix(blade.fallbackPrefix);
+ if (blade.tabCompleter != null)
+ blade.commandService.setTabCompleter(blade.tabCompleter);
+
for (Binding binding : blade.bindings) {
binding.bind(blade.commandService);
}
+ blade.commandService.getTabCompleter().init(blade.commandService);
+
for (Map.Entry, Class extends ProviderAnnotation>>, BladeProvider>> entry : blade.customProviderMap.entrySet()) {
//noinspection deprecation
blade.commandService.bindProviderUnsafely(entry.getKey().getKey(), entry.getValue(), entry.getKey().getValue());
diff --git a/src/main/java/me/vaperion/blade/command/bindings/impl/BukkitBindings.java b/src/main/java/me/vaperion/blade/command/bindings/impl/BukkitBindings.java
index 84f6f60..6e7e748 100644
--- a/src/main/java/me/vaperion/blade/command/bindings/impl/BukkitBindings.java
+++ b/src/main/java/me/vaperion/blade/command/bindings/impl/BukkitBindings.java
@@ -8,6 +8,7 @@
import me.vaperion.blade.command.service.BladeCommandService;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
+import org.bukkit.GameMode;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -15,6 +16,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.UUID;
import java.util.regex.Pattern;
@@ -81,6 +83,37 @@ public List suggest(@NotNull BladeContext context, @NotNull String input
return offlinePlayer;
});
+
+ commandService.bindProvider(GameMode.class, new BladeProvider() {
+ @Nullable
+ @Override
+ public GameMode provide(@NotNull BladeContext ctx, @NotNull BladeParameter param, @Nullable String input) throws BladeExitMessage {
+ if (input == null) return null;
+ input = input.trim();
+
+ GameMode mode = getGameMode(input);
+
+ if (mode == null)
+ throw new BladeExitMessage("No game mode with name " + ChatColor.YELLOW + input + ChatColor.RED + " found.");
+
+ return mode;
+ }
+
+ @NotNull
+ @Override
+ public List suggest(@NotNull BladeContext context, @NotNull String input) throws BladeExitMessage {
+ input = input.toUpperCase(Locale.ROOT);
+ List completions = new ArrayList<>();
+
+ for (GameMode mode : GameMode.values()) {
+ if (mode.name().startsWith(input)) {
+ completions.add(mode.name().toLowerCase(Locale.ROOT));
+ }
+ }
+
+ return completions;
+ }
+ });
}
private boolean isUUID(@NotNull String input) {
@@ -99,4 +132,16 @@ private OfflinePlayer getOfflinePlayer(@NotNull String input) {
return Bukkit.getOfflinePlayer(input);
}
+ @Nullable
+ private GameMode getGameMode(String input) {
+ input = input.toUpperCase(Locale.ROOT);
+
+ for (GameMode mode : GameMode.values()) {
+ if (mode.name().startsWith(input) || input.equals(String.valueOf(mode.getValue()))) {
+ return mode;
+ }
+ }
+
+ return null;
+ }
}
diff --git a/src/main/java/me/vaperion/blade/command/container/BladeCommand.java b/src/main/java/me/vaperion/blade/command/container/BladeCommand.java
index 15d7dfa..872a450 100644
--- a/src/main/java/me/vaperion/blade/command/container/BladeCommand.java
+++ b/src/main/java/me/vaperion/blade/command/container/BladeCommand.java
@@ -31,7 +31,7 @@ public class BladeCommand {
private final boolean senderParameter;
private final List parameters = new LinkedList<>();
- private final List> providers = new LinkedList<>();
+ private final List> providers = new LinkedList<>(), parameterProviders = new LinkedList<>(), flagProviders = new LinkedList<>();
public BladeCommand(BladeCommandService commandService, Object instance, Method method, String[] aliases, Command command, Permission permission) {
this.commandService = commandService;
@@ -70,8 +70,16 @@ public BladeCommand(BladeCommandService commandService, Object instance, Method
bladeParameter = new BladeParameter.CommandParameter(parameterName, parameter.getType(), parameter.getAnnotation(Optional.class), parameter.getAnnotation(Range.class), parameter.isAnnotationPresent(Combined.class));
}
+ BladeProvider> provider = commandService.getCommandResolver().resolveProvider(parameter.getType(), Arrays.asList(parameter.getAnnotations()));
+
parameters.add(bladeParameter);
- providers.add(commandService.getCommandResolver().resolveProvider(parameter.getType(), Arrays.asList(parameter.getAnnotations())));
+ providers.add(provider);
+
+ if (bladeParameter instanceof BladeParameter.FlagParameter)
+ flagProviders.add(provider);
+ else
+ parameterProviders.add(provider);
+
i++;
}
}
diff --git a/src/main/java/me/vaperion/blade/command/container/impl/BukkitCommandContainer.java b/src/main/java/me/vaperion/blade/command/container/impl/BukkitCommandContainer.java
index 065c62e..3a3cc6e 100644
--- a/src/main/java/me/vaperion/blade/command/container/impl/BukkitCommandContainer.java
+++ b/src/main/java/me/vaperion/blade/command/container/impl/BukkitCommandContainer.java
@@ -2,7 +2,6 @@
import lombok.Getter;
import me.vaperion.blade.command.annotation.Flag;
-import me.vaperion.blade.command.argument.BladeProvider;
import me.vaperion.blade.command.container.BladeCommand;
import me.vaperion.blade.command.container.BladeParameter;
import me.vaperion.blade.command.container.ContainerCreator;
@@ -64,14 +63,12 @@ public BukkitCommandContainer(@NotNull BladeCommandService service, @NotNull Bla
SimplePluginManager simplePluginManager = (SimplePluginManager) Bukkit.getServer().getPluginManager();
SimpleCommandMap simpleCommandMap = (SimpleCommandMap) COMMAND_MAP.get(simplePluginManager);
- simpleCommandMap.register(Optional.ofNullable(this.commandService.getFallbackPrefix()).orElse(alias), this);
+ simpleCommandMap.register(this.commandService.getFallbackPrefix(), this);
}
- @NotNull
+ @Nullable
private Tuple resolveCommand(@NotNull String[] arguments) throws BladeExitMessage {
- return Optional
- .ofNullable(commandService.getCommandResolver().resolveCommand(parentCommand, arguments))
- .orElseThrow(() -> new BladeExitMessage("This command failed to execute as we couldn't find it's registration."));
+ return commandService.getCommandResolver().resolveCommand(arguments);
}
@NotNull
@@ -148,19 +145,20 @@ private Tuple checkPermission(@NotNull CommandSender sender, @N
}
private String[] joinAliasToArgs(String alias, String[] args) {
- String[] argsWithAlias = new String[args.length + 1];
- argsWithAlias[0] = alias;
- System.arraycopy(args, 0, argsWithAlias, 1, args.length);
+ String[] aliasParts = alias.split(" ");
+ String[] argsWithAlias = new String[args.length + aliasParts.length];
+ System.arraycopy(aliasParts, 0, argsWithAlias, 0, aliasParts.length);
+ System.arraycopy(args, 0, argsWithAlias, aliasParts.length, args.length);
return argsWithAlias;
}
@Override
- public boolean testPermissionSilent(CommandSender sender) {
+ public boolean testPermissionSilent(@NotNull CommandSender sender) {
return hasPermission(sender, new String[0]);
}
@Override
- public boolean execute(CommandSender sender, String alias, String[] args) {
+ public boolean execute(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) {
BladeCommand command = null;
String resolvedAlias = alias;
@@ -169,6 +167,8 @@ public boolean execute(CommandSender sender, String alias, String[] args) {
if (!permissionResult.getLeft()) throw new BladeExitMessage(permissionResult.getRight());
Tuple resolved = resolveCommand(joinAliasToArgs(alias, args));
+ if (resolved == null) throw new BladeExitMessage("This command failed to execute as we couldn't find it's registration.");
+
command = resolved.getLeft();
resolvedAlias = resolved.getRight();
int offset = Math.min(args.length, resolvedAlias.split(" ").length - 1);
@@ -208,20 +208,30 @@ public boolean execute(CommandSender sender, String alias, String[] args) {
return false;
}
+ @NotNull
@Override
- public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
+ public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
+ if (!commandService.getTabCompleter().isDefault()) return Collections.emptyList();
if (!hasPermission(sender, args)) return Collections.emptyList();
try {
- BladeCommand command = resolveCommand(joinAliasToArgs(alias, args)).getLeft();
- BladeContext context = new BladeContext(new BukkitSender(sender), alias, args);
+ Tuple resolved = resolveCommand(joinAliasToArgs(alias, args));
+ if (resolved == null) {
+ // maybe suggest subcommands?
+ return Collections.emptyList();
+ }
+
+ BladeCommand command = resolved.getLeft();
+ String foundAlias = resolved.getRight();
+
+ List argList = new ArrayList<>(Arrays.asList(args));
+ if (foundAlias.split(" ").length > 1) argList.subList(0, foundAlias.split(" ").length - 1).clear();
- Tuple, String> data = commandService.getCommandCompleter().getLastProvider(command, args);
- BladeProvider> provider = data == null ? null : data.getLeft();
- String argument = data == null ? null : data.getRight();
- if (provider == null) return Collections.emptyList();
+ if (argList.isEmpty()) argList.add("");
+ String[] actualArguments = argList.toArray(new String[0]);
- return provider.suggest(context, argument);
+ BladeContext context = new BladeContext(new BukkitSender(sender), foundAlias, actualArguments);
+ return commandService.getCommandCompleter().suggest(context, command, actualArguments);
} catch (BladeExitMessage ex) {
sender.sendMessage(ChatColor.RED + ex.getMessage());
} catch (Exception ex) {
diff --git a/src/main/java/me/vaperion/blade/command/service/BladeCommandCompleter.java b/src/main/java/me/vaperion/blade/command/service/BladeCommandCompleter.java
index 097233a..80d190b 100644
--- a/src/main/java/me/vaperion/blade/command/service/BladeCommandCompleter.java
+++ b/src/main/java/me/vaperion/blade/command/service/BladeCommandCompleter.java
@@ -3,15 +3,16 @@
import lombok.RequiredArgsConstructor;
import me.vaperion.blade.command.argument.BladeProvider;
import me.vaperion.blade.command.container.BladeCommand;
-import me.vaperion.blade.command.container.BladeParameter;
+import me.vaperion.blade.command.context.BladeContext;
+import me.vaperion.blade.command.context.WrappedSender;
import me.vaperion.blade.command.exception.BladeExitMessage;
import me.vaperion.blade.utils.Tuple;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Supplier;
@RequiredArgsConstructor
public class BladeCommandCompleter {
@@ -19,36 +20,48 @@ public class BladeCommandCompleter {
private final BladeCommandService commandService;
@Nullable
- public Tuple, String> getLastProvider(@NotNull BladeCommand command, @NotNull String[] args) throws BladeExitMessage {
- BladeProvider> lastProvider = null;
- String lastArgument = null;
+ public List suggest(@NotNull String commandLine, @NotNull Supplier> senderSupplier, @NotNull Function permissionFunction) {
+ String[] commandParts = commandLine.split(" ");
+ Tuple resolved = commandService.getCommandResolver().resolveCommand(commandParts);
+ if (resolved == null) return null;
+ if (!permissionFunction.apply(resolved.getLeft())) return Collections.emptyList();
+
+ BladeCommand command = resolved.getLeft();
+ String foundAlias = resolved.getRight();
+
+ List argList = new ArrayList<>(Arrays.asList(commandParts));
+ argList.subList(0, Math.max(1, foundAlias.split(" ").length)).clear();
+
+ if (commandLine.endsWith(" ")) argList.add("");
+ String[] actualArguments = argList.toArray(new String[0]);
+
+ BladeContext context = new BladeContext(senderSupplier.get(), foundAlias, actualArguments);
+ return suggest(context, command, actualArguments);
+ }
+
+ @NotNull
+ public List suggest(@NotNull BladeContext context, @NotNull BladeCommand command, @NotNull String[] args) throws BladeExitMessage {
try {
List argumentList = new ArrayList<>(Arrays.asList(args));
List arguments = command.isQuoted() ? commandService.getCommandParser().combineQuotedArguments(argumentList) : argumentList;
- int argIndex = 0, providerIndex = 0;
- for (BladeParameter parameter : command.getParameters()) {
- boolean flag = false;
-
- if (parameter instanceof BladeParameter.FlagParameter) {
- flag = true;
- } else {
- if (arguments.size() <= argIndex) return new Tuple<>(lastProvider, lastArgument);
- }
+ Map flags = commandService.getCommandParser().parseFlags(command, arguments);
+ for (Map.Entry entry : flags.entrySet()) {
+ arguments.remove("-" + entry.getKey());
- BladeProvider> provider = command.getProviders().get(providerIndex);
- if (provider == null)
- throw new BladeExitMessage("Could not find provider for type '" + parameter.getType().getCanonicalName() + "'.");
+ boolean isFlag = command.getFlagParameters().stream().anyMatch(flag -> flag.getFlag().value() == entry.getKey());
+ if (!isFlag || !"true".equals(entry.getValue())) arguments.remove(entry.getValue());
+ }
- lastProvider = provider;
- lastArgument = (arguments.size() - 1 >= argIndex) ? arguments.get(argIndex) : null;
+ if (arguments.size() == 0) return Collections.emptyList();
+ if (command.getParameterProviders().size() < arguments.size()) return Collections.emptyList();
- if (!flag) argIndex++;
- providerIndex++;
- }
+ int index = Math.max(0, arguments.size() - 1);
+ BladeProvider> parameterProvider = command.getParameterProviders().get(index);
+ String argument = index < arguments.size() ? arguments.get(index) : "";
- return new Tuple<>(lastProvider, lastArgument);
+ return parameterProvider.suggest(context, argument);
} catch (BladeExitMessage ex) {
throw ex;
} catch (Exception ex) {
diff --git a/src/main/java/me/vaperion/blade/command/service/BladeCommandParser.java b/src/main/java/me/vaperion/blade/command/service/BladeCommandParser.java
index da9b6c1..13c4c89 100644
--- a/src/main/java/me/vaperion/blade/command/service/BladeCommandParser.java
+++ b/src/main/java/me/vaperion/blade/command/service/BladeCommandParser.java
@@ -129,7 +129,10 @@ public List combineQuotedArguments(@NotNull List args) {
for (int i = 0; i < args.size(); i++) {
String arg = args.get(i);
- if (arg.isEmpty()) continue;
+ if (arg.isEmpty()) {
+ argList.add(arg);
+ continue;
+ }
char c = arg.charAt(0);
if (c == '"' || c == '\'') {
@@ -157,7 +160,7 @@ public List combineQuotedArguments(@NotNull List args) {
}
}
- if (!arg.isEmpty()) argList.add(arg);
+ argList.add(arg);
}
return argList;
diff --git a/src/main/java/me/vaperion/blade/command/service/BladeCommandRegistrar.java b/src/main/java/me/vaperion/blade/command/service/BladeCommandRegistrar.java
index c21d44e..03f865b 100644
--- a/src/main/java/me/vaperion/blade/command/service/BladeCommandRegistrar.java
+++ b/src/main/java/me/vaperion/blade/command/service/BladeCommandRegistrar.java
@@ -12,7 +12,6 @@
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
-import java.util.stream.Collectors;
@RequiredArgsConstructor
public class BladeCommandRegistrar {
@@ -45,16 +44,15 @@ public void registerClass(@Nullable Object instance, @NotNull Class> clazz) {
public void registerMethod(@Nullable Object instance, @NotNull Method method, @Nullable BladeCommand parentCommand) throws Exception {
Command command = method.getAnnotation(Command.class);
Permission permission = method.getAnnotation(Permission.class);
+
String[] aliases = parentCommand == null ? command.value() : mutateAliases(command.value(), parentCommand.getAliases());
+ aliases = Arrays.stream(aliases).map(String::toLowerCase).toArray(String[]::new);
- BladeCommand bladeCommand = new BladeCommand(commandService, instance, method, Arrays.stream(aliases).map(String::toLowerCase).toArray(String[]::new), command, permission);
+ BladeCommand bladeCommand = new BladeCommand(commandService, instance, method, aliases, command, permission);
commandService.commands.add(bladeCommand);
- List aliasList = Arrays.stream(aliases).collect(Collectors.toCollection(LinkedList::new));
-
- for (String alias : aliasList) {
- String realAlias = alias;
- if (alias.contains(" ")) realAlias = alias.split(" ")[0];
+ for (String alias : aliases) {
+ String realAlias = alias.split(" ")[0];
commandService.aliasCommands.computeIfAbsent(realAlias, $ -> new LinkedList<>()).add(bladeCommand);
diff --git a/src/main/java/me/vaperion/blade/command/service/BladeCommandResolver.java b/src/main/java/me/vaperion/blade/command/service/BladeCommandResolver.java
index a695c5f..9e90863 100644
--- a/src/main/java/me/vaperion/blade/command/service/BladeCommandResolver.java
+++ b/src/main/java/me/vaperion/blade/command/service/BladeCommandResolver.java
@@ -9,10 +9,9 @@
import org.jetbrains.annotations.Nullable;
import java.lang.annotation.Annotation;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicReference;
@RequiredArgsConstructor
public class BladeCommandResolver {
@@ -20,39 +19,26 @@ public class BladeCommandResolver {
private final BladeCommandService commandService;
@Nullable
- public Tuple resolveCommand(@NotNull BladeCommand parentCommand, @NotNull String[] arguments) {
- String cmd = String.join(" ", arguments);
+ public Tuple resolveCommand(@NotNull String[] input) {
+ if (input.length == 0) return null;
+ String[] commandParts = Arrays.copyOf(input, input.length);
- do {
- AtomicReference commandRef = new AtomicReference<>();
-
- for (String alias : parentCommand.getRealAliases()) {
- final String finalCmd = cmd;
+ String baseCommand = commandParts[0].toLowerCase();
+ List tree = commandService.aliasCommands.getOrDefault(baseCommand, new ArrayList<>());
- Optional.ofNullable(commandService.aliasCommands.get(alias))
- .ifPresent(list -> {
- if (commandRef.get() != null) return;
+ do {
+ String checking = String.join(" ", commandParts);
- list.forEach(command -> {
- for (String commandAlias : command.getAliases()) {
- if (commandAlias.equalsIgnoreCase(finalCmd)) {
- commandRef.set(command);
- break;
- }
- }
- });
- });
+ for (BladeCommand subCommand : tree) {
+ for (String commandAlias : subCommand.getAliases()) {
+ if (commandAlias.equalsIgnoreCase(checking)) return new Tuple<>(subCommand, commandAlias);
+ }
}
- BladeCommand bladeCommand = commandRef.get();
- if (bladeCommand != null) return new Tuple<>(bladeCommand, cmd);
-
- String[] parts = cmd.split(" ");
- if (parts.length <= 1) cmd = "";
- else cmd = String.join(" ", Arrays.copyOfRange(parts, 0, parts.length - 1));
- } while (!cmd.isEmpty());
+ commandParts = Arrays.copyOfRange(commandParts, 0, commandParts.length - 1);
+ } while (commandParts.length > 0);
- return new Tuple<>(parentCommand, parentCommand.getRealAliases()[0]);
+ return null;
}
@SuppressWarnings("unchecked")
diff --git a/src/main/java/me/vaperion/blade/command/service/BladeCommandService.java b/src/main/java/me/vaperion/blade/command/service/BladeCommandService.java
index 0ddc895..3c3c5f3 100644
--- a/src/main/java/me/vaperion/blade/command/service/BladeCommandService.java
+++ b/src/main/java/me/vaperion/blade/command/service/BladeCommandService.java
@@ -9,6 +9,8 @@
import me.vaperion.blade.command.container.BladeProviderContainer;
import me.vaperion.blade.command.container.ContainerCreator;
import me.vaperion.blade.command.container.ICommandContainer;
+import me.vaperion.blade.completer.TabCompleter;
+import me.vaperion.blade.completer.impl.DefaultTabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -24,8 +26,9 @@ public class BladeCommandService {
final Map> aliasCommands = new LinkedHashMap<>();
final Map containerMap = new LinkedHashMap<>();
- @Setter @Getter private String fallbackPrefix = null;
+ @Setter @Getter private String fallbackPrefix = "blade";
@Setter @Getter private ContainerCreator> containerCreator = ContainerCreator.NONE;
+ @Setter @Getter private TabCompleter tabCompleter = new DefaultTabCompleter();
@Getter private final BladeCommandRegistrar commandRegistrar = new BladeCommandRegistrar(this);
@Getter private final BladeCommandResolver commandResolver = new BladeCommandResolver(this);
diff --git a/src/main/java/me/vaperion/blade/completer/TabCompleter.java b/src/main/java/me/vaperion/blade/completer/TabCompleter.java
new file mode 100644
index 0000000..cd08ba4
--- /dev/null
+++ b/src/main/java/me/vaperion/blade/completer/TabCompleter.java
@@ -0,0 +1,20 @@
+package me.vaperion.blade.completer;
+
+import me.vaperion.blade.command.container.BladeCommand;
+import me.vaperion.blade.command.service.BladeCommandService;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public interface TabCompleter {
+ void init(@NotNull BladeCommandService commandService);
+
+ default boolean isDefault() {
+ return false;
+ }
+
+ default boolean hasPermission(@NotNull Player player, @NotNull BladeCommand command) {
+ if ("op".equals(command.getPermission())) return player.isOp();
+ if (command.getPermission() == null || command.getPermission().trim().isEmpty()) return true;
+ return player.hasPermission(command.getPermission());
+ }
+}
diff --git a/src/main/java/me/vaperion/blade/completer/impl/DefaultTabCompleter.java b/src/main/java/me/vaperion/blade/completer/impl/DefaultTabCompleter.java
new file mode 100644
index 0000000..6d61d8d
--- /dev/null
+++ b/src/main/java/me/vaperion/blade/completer/impl/DefaultTabCompleter.java
@@ -0,0 +1,17 @@
+package me.vaperion.blade.completer.impl;
+
+import me.vaperion.blade.command.service.BladeCommandService;
+import me.vaperion.blade.completer.TabCompleter;
+import org.jetbrains.annotations.NotNull;
+
+public class DefaultTabCompleter implements TabCompleter {
+ @Override
+ public void init(@NotNull BladeCommandService commandService) {
+ // noop
+ }
+
+ @Override
+ public boolean isDefault() {
+ return true;
+ }
+}
diff --git a/src/main/java/me/vaperion/blade/completer/impl/ProtocolLibTabCompleter.java b/src/main/java/me/vaperion/blade/completer/impl/ProtocolLibTabCompleter.java
new file mode 100644
index 0000000..2885227
--- /dev/null
+++ b/src/main/java/me/vaperion/blade/completer/impl/ProtocolLibTabCompleter.java
@@ -0,0 +1,54 @@
+package me.vaperion.blade.completer.impl;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketEvent;
+import me.vaperion.blade.command.context.impl.BukkitSender;
+import me.vaperion.blade.command.service.BladeCommandService;
+import me.vaperion.blade.completer.TabCompleter;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class ProtocolLibTabCompleter extends PacketAdapter implements TabCompleter {
+
+ private BladeCommandService commandService;
+
+ public ProtocolLibTabCompleter(@NotNull JavaPlugin plugin) {
+ super(plugin, PacketType.Play.Client.TAB_COMPLETE);
+ }
+
+ @Override
+ public void init(@NotNull BladeCommandService commandService) {
+ this.commandService = commandService;
+ ProtocolLibrary.getProtocolManager().addPacketListener(this);
+ }
+
+ @Override
+ public void onPacketReceiving(PacketEvent event) {
+ if (event.getPlayer() == null) return;
+
+ Player player = event.getPlayer();
+ String commandLine = event.getPacket().getStrings().read(0);
+
+ if (!commandLine.startsWith("/")) return;
+ else commandLine = commandLine.substring(1);
+
+ List suggestions = commandService.getCommandCompleter().suggest(commandLine, () -> new BukkitSender(player), cmd -> hasPermission(player, cmd));
+ if (suggestions == null) return; // if command was not found
+
+ try {
+ event.setCancelled(true);
+ PacketContainer tabComplete = new PacketContainer(PacketType.Play.Server.TAB_COMPLETE);
+ tabComplete.getStringArrays().write(0, suggestions.toArray(new String[0]));
+ ProtocolLibrary.getProtocolManager().sendServerPacket(player, tabComplete);
+ } catch (Exception ex) {
+ System.err.println("An exception was thrown while attempting to tab complete '" + commandLine + "' for player " + player.getName());
+ ex.printStackTrace();
+ }
+ }
+}