Added support for suggestions tabcompletions of a command input
This commit is contained in:
parent
eba88b2773
commit
f372eb3b98
12 changed files with 186 additions and 10 deletions
|
@ -2,6 +2,7 @@ package com.mojang.brigadier;
|
|||
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.context.CommandContextBuilder;
|
||||
|
@ -12,6 +13,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
|
|||
import com.mojang.brigadier.tree.RootCommandNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -23,7 +25,7 @@ public class CommandDispatcher<T> {
|
|||
}
|
||||
};
|
||||
|
||||
public static final SimpleCommandExceptionType ERROR_UNKNOWN_COMMAND = new SimpleCommandExceptionType("unknown_command", "Unknown command");
|
||||
public static final SimpleCommandExceptionType ERROR_UNKNOWN_COMMAND = new SimpleCommandExceptionType("command.unknown", "Unknown command");
|
||||
public static final String ARGUMENT_SEPARATOR = " ";
|
||||
private static final String USAGE_OPTIONAL_OPEN = "[";
|
||||
private static final String USAGE_OPTIONAL_CLOSE = "]";
|
||||
|
@ -52,7 +54,11 @@ public class CommandDispatcher<T> {
|
|||
if (child.getCommand() != null) {
|
||||
context.withCommand(child.getCommand());
|
||||
}
|
||||
return parseNodes(child, remaining, context);
|
||||
if (remaining.isEmpty()) {
|
||||
return context.build();
|
||||
} else {
|
||||
return parseNodes(child, remaining.substring(1), context);
|
||||
}
|
||||
} catch (CommandException ex) {
|
||||
exception = ex;
|
||||
}
|
||||
|
@ -106,4 +112,28 @@ public class CommandDispatcher<T> {
|
|||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private Set<String> findSuggestions(CommandNode node, String command, CommandContextBuilder<T> contextBuilder, Set<String> result) {
|
||||
for (CommandNode child : node.getChildren()) {
|
||||
try {
|
||||
CommandContextBuilder<T> context = contextBuilder.copy();
|
||||
String remaining = child.parse(command, context);
|
||||
if (remaining.isEmpty()) {
|
||||
child.listSuggestions(command, result);
|
||||
} else {
|
||||
return findSuggestions(child, remaining.substring(1), context, result);
|
||||
}
|
||||
} catch (CommandException e) {
|
||||
child.listSuggestions(command, result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String[] getCompletionSuggestions(String command, T source) {
|
||||
final Set<String> nodes = findSuggestions(root, command, new CommandContextBuilder<>(source), Sets.newLinkedHashSet());
|
||||
|
||||
return nodes.toArray(new String[nodes.size()]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@ package com.mojang.brigadier.arguments;
|
|||
import com.mojang.brigadier.context.ParsedArgument;
|
||||
import com.mojang.brigadier.exceptions.CommandException;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface CommandArgumentType<T> {
|
||||
ParsedArgument<T> parse(String command) throws CommandException;
|
||||
|
||||
void listSuggestions(String command, Set<String> output);
|
||||
}
|
||||
|
|
|
@ -7,10 +7,12 @@ import com.mojang.brigadier.context.ParsedArgument;
|
|||
import com.mojang.brigadier.exceptions.CommandException;
|
||||
import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class IntegerArgumentType implements CommandArgumentType<Integer> {
|
||||
public static final ParameterizedCommandExceptionType ERROR_NOT_A_NUMBER = new ParameterizedCommandExceptionType("argument-integer-invalid", "Expected an integer, found '${found}'", "found");
|
||||
public static final ParameterizedCommandExceptionType ERROR_TOO_SMALL = new ParameterizedCommandExceptionType("argument-integer-low", "Integer must not be less than ${minimum}, found ${found}", "found", "minimum");
|
||||
public static final ParameterizedCommandExceptionType ERROR_TOO_BIG = new ParameterizedCommandExceptionType("argument-integer-big", "Integer must not be more than ${maximum}, found ${found}", "found", "maximum");
|
||||
public static final ParameterizedCommandExceptionType ERROR_NOT_A_NUMBER = new ParameterizedCommandExceptionType("argument.integer.invalid", "Expected an integer, found '${found}'", "found");
|
||||
public static final ParameterizedCommandExceptionType ERROR_TOO_SMALL = new ParameterizedCommandExceptionType("argument.integer.low", "Integer must not be less than ${minimum}, found ${found}", "found", "minimum");
|
||||
public static final ParameterizedCommandExceptionType ERROR_TOO_BIG = new ParameterizedCommandExceptionType("argument.integer.big", "Integer must not be more than ${maximum}, found ${found}", "found", "maximum");
|
||||
|
||||
private static final Splitter SPLITTER = Splitter.on(CommandDispatcher.ARGUMENT_SEPARATOR).limit(2);
|
||||
|
||||
|
@ -58,6 +60,10 @@ public class IntegerArgumentType implements CommandArgumentType<Integer> {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listSuggestions(String command, Set<String> output) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -6,6 +6,8 @@ import com.mojang.brigadier.context.CommandContextBuilder;
|
|||
import com.mojang.brigadier.context.ParsedArgument;
|
||||
import com.mojang.brigadier.exceptions.CommandException;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class ArgumentCommandNode<T> extends CommandNode {
|
||||
private static final String USAGE_ARGUMENT_OPEN = "<";
|
||||
private static final String USAGE_ARGUMENT_CLOSE = ">";
|
||||
|
@ -46,12 +48,17 @@ public class ArgumentCommandNode<T> extends CommandNode {
|
|||
contextBuilder.withNode(this, parsed.getRaw());
|
||||
|
||||
if (command.length() > start) {
|
||||
return command.substring(start + 1);
|
||||
return command.substring(start);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listSuggestions(String command, Set<String> output) {
|
||||
type.listSuggestions(command, output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.mojang.brigadier.exceptions.CommandException;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class CommandNode {
|
||||
private final Map<Object, CommandNode> children = Maps.newLinkedHashMap();
|
||||
|
@ -62,4 +63,6 @@ public abstract class CommandNode {
|
|||
public abstract String getUsageText();
|
||||
|
||||
public abstract String parse(String command, CommandContextBuilder<?> contextBuilder) throws CommandException;
|
||||
|
||||
public abstract void listSuggestions(String command, Set<String> output);
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ import com.mojang.brigadier.context.CommandContextBuilder;
|
|||
import com.mojang.brigadier.exceptions.CommandException;
|
||||
import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class LiteralCommandNode extends CommandNode {
|
||||
public static final ParameterizedCommandExceptionType ERROR_INCORRECT_LITERAL = new ParameterizedCommandExceptionType("incorrect_literal", "Expected literal ${expected}", "expected");
|
||||
public static final ParameterizedCommandExceptionType ERROR_INCORRECT_LITERAL = new ParameterizedCommandExceptionType("argument.literal.incorrect", "Expected literal ${expected}", "expected");
|
||||
|
||||
private final String literal;
|
||||
|
||||
|
@ -34,10 +36,17 @@ public class LiteralCommandNode extends CommandNode {
|
|||
}
|
||||
|
||||
contextBuilder.withNode(this, literal);
|
||||
int start = expected.length();
|
||||
int start = literal.length();
|
||||
return command.substring(start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listSuggestions(String command, Set<String> output) {
|
||||
if (literal.startsWith(command)) {
|
||||
output.add(literal);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -3,6 +3,8 @@ package com.mojang.brigadier.tree;
|
|||
import com.mojang.brigadier.context.CommandContextBuilder;
|
||||
import com.mojang.brigadier.exceptions.CommandException;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class RootCommandNode extends CommandNode {
|
||||
public RootCommandNode() {
|
||||
super(null);
|
||||
|
@ -23,6 +25,10 @@ public class RootCommandNode extends CommandNode {
|
|||
return command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listSuggestions(String command, Set<String> output) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package com.mojang.brigadier;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
|
||||
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
|
||||
import static org.hamcrest.Matchers.emptyArray;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasToString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CommandDispatcherCompletionsTest {
|
||||
private CommandDispatcher<Object> subject;
|
||||
@Mock
|
||||
private Object source;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
subject = new CommandDispatcher<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCommands() throws Exception {
|
||||
assertThat(subject.getCompletionSuggestions("", source), is(emptyArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommand() throws Exception {
|
||||
subject.register(literal("foo"));
|
||||
subject.register(literal("bar"));
|
||||
assertThat(subject.getCompletionSuggestions("", source), equalTo(new String[] {"foo", "bar"}));
|
||||
assertThat(subject.getCompletionSuggestions("f", source), equalTo(new String[] {"foo"}));
|
||||
assertThat(subject.getCompletionSuggestions("b", source), equalTo(new String[] {"bar"}));
|
||||
assertThat(subject.getCompletionSuggestions("q", source), is(emptyArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubCommand() throws Exception {
|
||||
subject.register(literal("foo").then(literal("abc")).then(literal("def")));
|
||||
subject.register(literal("bar"));
|
||||
assertThat(subject.getCompletionSuggestions("", source), equalTo(new String[] {"foo", "bar"}));
|
||||
assertThat(subject.getCompletionSuggestions("f", source), equalTo(new String[] {"foo"}));
|
||||
assertThat(subject.getCompletionSuggestions("foo", source), equalTo(new String[] {"foo"}));
|
||||
assertThat(subject.getCompletionSuggestions("foo ", source), equalTo(new String[] {"abc", "def"}));
|
||||
assertThat(subject.getCompletionSuggestions("foo a", source), equalTo(new String[] {"abc"}));
|
||||
assertThat(subject.getCompletionSuggestions("foo d", source), equalTo(new String[] {"def"}));
|
||||
assertThat(subject.getCompletionSuggestions("foo g", source), is(emptyArray()));
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package com.mojang.brigadier.arguments;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.testing.EqualsTester;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.context.CommandContextBuilder;
|
||||
|
@ -11,8 +12,10 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.hasToString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
@ -90,6 +93,13 @@ public class IntegerArgumentTypeTest {
|
|||
assertThat(IntegerArgumentType.getInteger(context, "foo"), is(100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestions() throws Exception {
|
||||
Set<String> set = Sets.newHashSet();
|
||||
type.listSuggestions("", set);
|
||||
assertThat(set, is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() throws Exception {
|
||||
new EqualsTester()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.mojang.brigadier.tree;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.testing.EqualsTester;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
|
@ -11,9 +12,12 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
|
||||
import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
@ -36,7 +40,7 @@ public class ArgumentCommandNodeTest extends AbstractCommandNodeTest {
|
|||
|
||||
@Test
|
||||
public void testParse() throws Exception {
|
||||
assertThat(node.parse("123 456", contextBuilder), is("456"));
|
||||
assertThat(node.parse("123 456", contextBuilder), is(" 456"));
|
||||
|
||||
assertThat(contextBuilder.getArguments().containsKey("foo"), is(true));
|
||||
assertThat(contextBuilder.getArguments().get("foo").getResult(), is(123));
|
||||
|
@ -66,6 +70,13 @@ public class ArgumentCommandNodeTest extends AbstractCommandNodeTest {
|
|||
assertThat(node.getUsageText(), is("<foo>"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestions() throws Exception {
|
||||
Set<String> set = Sets.newHashSet();
|
||||
node.listSuggestions("", set);
|
||||
assertThat(set, is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() throws Exception {
|
||||
Command command = mock(Command.class);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.mojang.brigadier.tree;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.testing.EqualsTester;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.context.CommandContextBuilder;
|
||||
|
@ -10,8 +11,11 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
@ -34,7 +38,7 @@ public class LiteralCommandNodeTest extends AbstractCommandNodeTest {
|
|||
|
||||
@Test
|
||||
public void testParse() throws Exception {
|
||||
assertThat(node.parse("foo bar", contextBuilder), is("bar"));
|
||||
assertThat(node.parse("foo bar", contextBuilder), is(" bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -69,6 +73,25 @@ public class LiteralCommandNodeTest extends AbstractCommandNodeTest {
|
|||
assertThat(node.getUsageText(), is("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestions() throws Exception {
|
||||
Set<String> set = Sets.newHashSet();
|
||||
node.listSuggestions("", set);
|
||||
assertThat(set, equalTo(Sets.newHashSet("foo")));
|
||||
|
||||
set.clear();
|
||||
node.listSuggestions("foo", set);
|
||||
assertThat(set, equalTo(Sets.newHashSet("foo")));
|
||||
|
||||
set.clear();
|
||||
node.listSuggestions("food", set);
|
||||
assertThat(set, is(empty()));
|
||||
|
||||
set.clear();
|
||||
node.listSuggestions("b", set);
|
||||
assertThat(set, is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() throws Exception {
|
||||
Command command = mock(Command.class);
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package com.mojang.brigadier.tree;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.testing.EqualsTester;
|
||||
import com.mojang.brigadier.context.CommandContextBuilder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
|
@ -37,6 +41,13 @@ public class RootCommandNodeTest extends AbstractCommandNodeTest {
|
|||
assertThat(node.getUsageText(), is(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestions() throws Exception {
|
||||
Set<String> set = Sets.newHashSet();
|
||||
node.listSuggestions("", set);
|
||||
assertThat(set, is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() throws Exception {
|
||||
new EqualsTester()
|
||||
|
|
Loading…
Reference in a new issue