@@ -84,54 +84,58 @@ private static <C, D, T> Collection<CommandNode<D>> createCommandNodes(
8484
8585 LiteralArgumentBuilder <D > builder = LiteralArgumentBuilder .literal (((LiteralCommand <C , T >) node ).getLiteral ());
8686 builder .requires (d -> node .isValid (contextConverter .apply (d )));
87- builder .executes (executor );
88-
89- for (Command <C , ?> subCommand : node .getSubCommands ()) {
90- createCommandNodes (List .of (subCommand ), suggestionProvider , executor , contextConverter ).forEach (builder ::then );
91- }
87+ if (node .getExecutable () != null ) builder .executes (executor );
88+ createCommandNodes (node .getSubCommands (), suggestionProvider , executor , contextConverter ).forEach (builder ::then );
9289
9390 commandNodes .add (builder .build ());
9491 }
9592
96- // arguments
97- Set <ArgumentCommand <C , T >> argumentNodes = nodes .stream ()
98- .filter (n -> n instanceof ArgumentCommand )
99- .map (n -> (ArgumentCommand <C , T >) n )
100- .collect (Collectors .toSet ());
101-
102- if (!argumentNodes .isEmpty ()) {
103- CommonNodeType commonNodeType = argumentNodes .stream ()
104- .map (CommonNodeType ::getFor )
105- .max (Comparator .comparing (CommonNodeType ::ordinal ))
106- .get ();
107-
108- String commonNodeName ;
109- if (argumentNodes .size () <= 3 ) {
110- commonNodeName = commonNodeType == CommonNodeType .GREEDY ? "..." : argumentNodes .stream ()
111- .map (ArgumentCommand ::getArgumentId )
112- .sorted ()
113- .collect (Collectors .joining ("|" ));
114- } else {
115- commonNodeName = "arg-" + UUID .randomUUID ().toString ().substring (0 , 8 );
116- }
117-
118- RequiredArgumentBuilder <D , ?> builder = RequiredArgumentBuilder .argument (commonNodeName , commonNodeType .getArgumentType ());
119- builder .suggests (suggestionProvider );
120- builder .requires (d -> {
121- C context = contextConverter .apply (d );
122- return argumentNodes .stream ().anyMatch (arg -> arg .isValid (context ));
123- });
124- builder .executes (executor );
125-
126- if (commonNodeType != CommonNodeType .GREEDY ) {
127- Collection <Command <C , T >> subCommands = argumentNodes .stream ()
128- .flatMap (c -> c .getSubCommands ().stream ())
129- .collect (Collectors .toSet ());
130- createCommandNodes (subCommands , suggestionProvider , executor , contextConverter ).forEach (builder ::then );
131- }
132-
133- commandNodes .add (builder .build ());
134- }
93+ // group arguments by brigadier-type
94+ // using an enum-map here sorts the arguments by their type as well (enum-ordinal) -> this is important
95+ EnumMap <CommonNodeType , Set <ArgumentCommand <C , T >>> typedArguments = nodes .stream ()
96+ .filter (c -> c instanceof ArgumentCommand )
97+ .map (c -> (ArgumentCommand <C , T >) c )
98+ .collect (Collectors .groupingBy (
99+ CommonNodeType ::getFor ,
100+ () -> new EnumMap <>(CommonNodeType .class ),
101+ Collectors .toSet ()
102+ ));
103+
104+ // group string-type arguments together further
105+ if (typedArguments .containsKey (CommonNodeType .WORD ) && typedArguments .containsKey (CommonNodeType .STRING ))
106+ typedArguments .get (CommonNodeType .STRING ).addAll (typedArguments .remove (CommonNodeType .WORD ));
107+ if (typedArguments .containsKey (CommonNodeType .STRING ) && typedArguments .containsKey (CommonNodeType .GREEDY ))
108+ typedArguments .get (CommonNodeType .GREEDY ).addAll (typedArguments .remove (CommonNodeType .STRING ));
109+
110+ typedArguments .forEach ((type , arguments ) -> {
111+
112+ RequiredArgumentBuilder <D , ?> builder = RequiredArgumentBuilder .argument (
113+ getCommonArgumentId (type , arguments ),
114+ type .getArgumentType ()
115+ );
116+
117+ // only allow suggestions for string-types
118+ if (type == CommonNodeType .GREEDY || type == CommonNodeType .STRING || type == CommonNodeType .WORD )
119+ builder .suggests (suggestionProvider );
120+
121+ builder .requires (d -> {
122+ C context = contextConverter .apply (d );
123+ return arguments .stream ().anyMatch (arg -> arg .isValid (context ));
124+ });
125+
126+ if (arguments .stream ().map (Command ::getExecutable ).anyMatch (Objects ::nonNull ))
127+ builder .executes (executor );
128+
129+ if (type != CommonNodeType .GREEDY ) {
130+ Collection <Command <C , T >> subCommands = arguments .stream ()
131+ .flatMap (c -> c .getSubCommands ().stream ())
132+ .collect (Collectors .toSet ());
133+ createCommandNodes (subCommands , suggestionProvider , executor , contextConverter ).forEach (builder ::then );
134+ }
135+
136+ commandNodes .add (builder .build ());
137+
138+ });
135139
136140 return commandNodes ;
137141 }
@@ -150,14 +154,28 @@ private static <C, T> void collectNodes(Command<C, T> command, Set<Command<C, T>
150154 nodes .add (command );
151155
152156 // handle optional commands
153- if (command instanceof ArgumentCommand && (( ArgumentCommand < C , T >) command ) .isOptional ()) {
157+ if (command .isOptional ()) {
154158 for (var subCommand : command .getSubCommands ()) {
155159 collectNodes (subCommand , nodes );
156160 }
157161 }
158162
159163 }
160164
165+ private static <C , T > String getCommonArgumentId (CommonNodeType type , Collection <ArgumentCommand <C , T >> arguments ) {
166+ String commonNodeName ;
167+ if (arguments .size () <= 3 ) {
168+ commonNodeName = type == CommonNodeType .GREEDY ? "..." : arguments .stream ()
169+ .map (ArgumentCommand ::getArgumentId )
170+ .distinct ()
171+ .sorted ()
172+ .collect (Collectors .joining ("|" ));
173+ } else {
174+ commonNodeName = "arg-" + UUID .randomUUID ().toString ().substring (0 , 8 );
175+ }
176+ return commonNodeName ;
177+ }
178+
161179 private enum CommonNodeType {
162180
163181 INTEGER (LongArgumentType .longArg ()),
0 commit comments