Skip to content
This repository was archived by the owner on Aug 31, 2019. It is now read-only.

Commit 8f006f3

Browse files
committed
Complete nested commands and fix player name completion not working
1 parent 9bf01e3 commit 8f006f3

2 files changed

Lines changed: 51 additions & 16 deletions

File tree

bukkit/src/main/java/com/sk89q/bukkit/util/DynamicPluginCommand.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,14 @@ public boolean execute(CommandSender sender, String label, String[] args) {
5757

5858
@Override
5959
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
60-
return completer == null ? super.tabComplete(sender, alias, args)
61-
: completer.onTabComplete(sender, this, alias, args);
60+
if(completer != null) {
61+
final List<String> completions = completer.onTabComplete(sender, this, alias, args);
62+
if(completions != null) {
63+
return completions;
64+
}
65+
}
66+
67+
return super.tabComplete(sender, alias, args);
6268
}
6369

6470
public CommandExecutor getExecutor() {

core/src/main/java/com/sk89q/minecraft/util/commands/CommandsManager.java

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.Set;
3434
import java.util.logging.Level;
3535
import java.util.logging.Logger;
36+
import javax.annotation.Nullable;
3637

3738
/**
3839
* Manager for handling commands. This allows you to easily process commands,
@@ -411,10 +412,15 @@ public void execute(String cmd, String[] args, T player, Object... methodArgs) t
411412
executeMethod(false, cmd, args, player, methodArgs);
412413
}
413414

414-
public List<String> complete(String cmd, String[] args, T player, Object... methodArgs) {
415+
/**
416+
* Attempt to complete a command.
417+
*
418+
* If null is returned, the server's default completion should be used.
419+
* Any non-null return should be used as the completion results, even if empty.
420+
*/
421+
public @Nullable List<String> complete(String cmd, String[] args, T player, Object... methodArgs) {
415422
try {
416-
final List<String> suggestions = executeMethod(true, cmd, args, player, methodArgs);
417-
return suggestions != null ? suggestions : Collections.<String>emptyList();
423+
return executeMethod(true, cmd, args, player, methodArgs);
418424
} catch(CommandException e) {
419425
return Collections.emptyList();
420426
}
@@ -439,14 +445,35 @@ private List<String> executeMethod(boolean completing, String cmd, String[] args
439445
* @param player the player
440446
* @param methodArgs the array of method arguments
441447
* @param level the depth of the command
448+
*
449+
* @return A list of completions, or null to use the server's default completion (player names).
450+
* Returning an empty list will prevent any completion from happening.
451+
*
442452
* @throws CommandException thrown on a command error
443453
*/
444-
private List<String> executeMethod(Method parent, boolean completing, String[] args, T player, Object[] methodArgs, int level) throws CommandException {
445-
String cmdName = args[level];
446454

447-
Map<String, Method> map = commands.get(parent);
448-
Method method = map.get(cmdName.toLowerCase());
455+
private List<String> executeMethod(Method parent, boolean completing, String[] args, T player, Object[] methodArgs, int level) throws CommandException {
456+
final String cmdName = args[level];
457+
final String cmdNameLower = cmdName.toLowerCase();
458+
final int argsCount = args.length - 1 - level;
459+
final Map<String, Method> map = commands.get(parent);
460+
461+
if(completing && argsCount == 0) {
462+
// Completing with no args means the command itself is being completed.
463+
// Gather all matching commands, that the player has permission to run,
464+
// and return them as completion options. If a full command is being
465+
// completed, it will be returned alone, which will just advance the cursor.
466+
final List<String> children = new ArrayList<>();
467+
for(Map.Entry<String, Method> entry : map.entrySet()) {
468+
final String child = entry.getKey();
469+
if(child.toLowerCase().startsWith(cmdNameLower) && hasPermission(entry.getValue(), player)) {
470+
children.add(child);
471+
}
472+
}
473+
return children;
474+
}
449475

476+
final Method method = map.get(cmdNameLower);
450477
if (method == null) {
451478
if (parent == null) { // Root
452479
throw new UnhandledCommandException();
@@ -457,12 +484,9 @@ private List<String> executeMethod(Method parent, boolean completing, String[] a
457484
}
458485

459486
if(!hasPermission(method, player)) {
460-
if(completing) return null;
461487
throw new CommandPermissionsException();
462488
}
463489

464-
int argsCount = args.length - 1 - level;
465-
466490
// checks if we need to execute the body of the nested command method (false)
467491
// or display the help what commands are available (true)
468492
// this is all for an args count of 0 if it is > 0 and a NestedCommand Annotation is present
@@ -471,10 +495,9 @@ private List<String> executeMethod(Method parent, boolean completing, String[] a
471495
// - /cmd - @NestedCommand(executeBody = true) will go into the else loop and execute code in that method
472496
// - /cmd <arg1> <arg2> - @NestedCommand(executeBody = true) will always go to the nested command class
473497
// - /cmd <arg1> - @NestedCommand(executeBody = false) will always go to the nested command class not matter the args
474-
boolean executeNested = method.isAnnotationPresent(NestedCommand.class)
475-
&& (argsCount > 0 || !method.getAnnotation(NestedCommand.class).executeBody());
498+
final NestedCommand nestedAnnot = method.getAnnotation(NestedCommand.class);
476499

477-
if (executeNested) {
500+
if (nestedAnnot != null && (argsCount > 0 || nestedAnnot.executeBody())) {
478501
if (argsCount == 0) {
479502
throw new MissingNestedCommandException("Sub-command required.",
480503
getNestedUsage(args, level, method, player));
@@ -487,6 +510,8 @@ private List<String> executeMethod(Method parent, boolean completing, String[] a
487510
} else {
488511
Command cmd = method.getAnnotation(Command.class);
489512

513+
// If the command method doesn't do completions, return null to indicate that
514+
// the default completion (player name) should be used.
490515
if(completing && !List.class.isAssignableFrom(method.getReturnType())) return null;
491516

492517
String[] newArgs = new String[args.length - level];
@@ -528,7 +553,11 @@ private List<String> executeMethod(Method parent, boolean completing, String[] a
528553

529554
Object instance = instances.get(method);
530555

531-
return invokeMethod(parent, args, player, method, instance, methodArgs, argsCount);
556+
// If we get here while completing, it means the method's return type is a List<String>,
557+
// and we never want to use the default completion. So if it returns null, convert it to
558+
// an empty list.
559+
final List<String> completions = invokeMethod(parent, args, player, method, instance, methodArgs, argsCount);
560+
return completions != null ? completions : Collections.<String>emptyList();
532561
}
533562
}
534563

0 commit comments

Comments
 (0)