3333import java .util .Set ;
3434import java .util .logging .Level ;
3535import 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