Reimplemented command suggestions

This commit is contained in:
Nathan Adams 2017-11-08 12:43:07 +01:00
parent 4d91bc6e7e
commit b48dbe7916
6 changed files with 163 additions and 100 deletions

View file

@ -3,7 +3,7 @@ import groovy.io.FileType
apply plugin: 'java-library'
apply plugin: 'maven'
version = '0.1.3'
version = '0.1.4'
group = 'com.mojang'
task wrapper(type: Wrapper) {

View file

@ -1,11 +1,11 @@
package com.mojang.brigadier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -19,10 +19,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -285,42 +285,37 @@ public class CommandDispatcher<S> {
return self;
}
private Set<String> findSuggestions(final CommandNode<S> node, final StringReader reader, final CommandContextBuilder<S> contextBuilder, final Set<String> result) {
if (node.getRedirect() != null) {
return findSuggestions(node.getRedirect(), reader, contextBuilder, result);
}
final S source = contextBuilder.getSource();
for (final CommandNode<S> child : node.getChildren()) {
if (!child.canUse(source)) {
continue;
}
final CommandContextBuilder<S> context = contextBuilder.copy();
final int cursor = reader.getCursor();
try {
child.parse(reader, context);
if (reader.canRead()) {
if (reader.peek() == ARGUMENT_SEPARATOR_CHAR) {
reader.skip();
return findSuggestions(child, reader, context, result);
}
} else {
reader.setCursor(cursor);
child.listSuggestions(reader.getRemaining(), result, context);
}
} catch (final CommandSyntaxException e) {
reader.setCursor(cursor);
child.listSuggestions(reader.getRemaining(), result, context);
}
public CommandSuggestions getCompletionSuggestions(final ParseResults<S> parse) {
final CommandContextBuilder<S> context = parse.getContext();
final Set<String> suggestions = new LinkedHashSet<>();
final CommandNode<S> parent;
final int start;
if (context.getNodes().isEmpty()) {
parent = root;
start = 0;
} else if (parse.getReader().canRead()) {
final Map.Entry<CommandNode<S>, StringRange> entry = Iterables.getLast(context.getNodes().entrySet());
parent = entry.getKey();
start = entry.getValue().getEnd() + 1;
} else if (context.getNodes().size() > 1) {
final Map.Entry<CommandNode<S>, StringRange> entry = Iterables.get(context.getNodes().entrySet(), context.getNodes().size() - 2);
parent = entry.getKey();
start = entry.getValue().getEnd() + 1;
} else {
parent = root;
start = 0;
}
return result;
}
for (final CommandNode<S> node : parent.getChildren()) {
node.listSuggestions(parse.getReader().getString().substring(start), suggestions, context);
}
public String[] getCompletionSuggestions(final String command, final S source) {
final StringReader reader = new StringReader(command);
final Set<String> nodes = findSuggestions(root, reader, new CommandContextBuilder<>(this, source, 0), Sets.newLinkedHashSet());
return nodes.toArray(new String[nodes.size()]);
final List<String> result = new ArrayList<>(suggestions);
Collections.sort(result);
return new CommandSuggestions(new StringRange(start, parse.getReader().getTotalLength()), result);
}
public RootCommandNode<S> getRoot() {

View file

@ -0,0 +1,45 @@
package com.mojang.brigadier;
import com.mojang.brigadier.context.StringRange;
import java.util.List;
import java.util.Objects;
public class CommandSuggestions {
private final StringRange range;
private final List<String> suggestions;
public CommandSuggestions(final StringRange range, final List<String> suggestions) {
this.range = range;
this.suggestions = suggestions;
}
public StringRange getRange() {
return range;
}
public List<String> getSuggestions() {
return suggestions;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CommandSuggestions)) {
return false;
}
final CommandSuggestions that = (CommandSuggestions) o;
return Objects.equals(range, that.range) && Objects.equals(suggestions, that.suggestions);
}
@Override
public int hashCode() {
return Objects.hash(range, suggestions);
}
public boolean isEmpty() {
return suggestions.isEmpty();
}
}

View file

@ -53,4 +53,12 @@ public class StringRange {
public int hashCode() {
return Objects.hash(start, end);
}
@Override
public String toString() {
return "StringRange{" +
"start=" + start +
", end=" + end +
'}';
}
}

View file

@ -1,64 +0,0 @@
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 java.util.Collections;
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.is;
import static org.junit.Assert.assertThat;
@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"));
subject.register(literal("baz").requires(s -> false));
assertThat(subject.getCompletionSuggestions("", source), equalTo(new String[]{"bar", "foo"}));
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 testCommand_redirect() throws Exception {
subject.register(literal("foo"));
subject.register(literal("bar"));
subject.register(literal("redirect").redirect(subject.getRoot(), Collections::singleton));
assertThat(subject.getCompletionSuggestions("redirect ", source), equalTo(new String[]{"bar", "foo", "redirect"}));
}
@Test
public void testSubCommand() throws Exception {
subject.register(literal("foo").then(literal("abc")).then(literal("def")).then(literal("ghi").requires(s -> false)));
subject.register(literal("bar"));
assertThat(subject.getCompletionSuggestions("", source), equalTo(new String[]{"bar", "foo"}));
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()));
}
}

View file

@ -0,0 +1,79 @@
package com.mojang.brigadier;
import com.google.common.collect.Lists;
import com.mojang.brigadier.context.StringRange;
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.builder.LiteralArgumentBuilder.literal;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
@RunWith(MockitoJUnitRunner.class)
public class CommandSuggestionsTest {
private CommandDispatcher<Object> subject;
@Mock
private Object source;
@Before
public void setUp() throws Exception {
subject = new CommandDispatcher<>();
}
@Test
public void getCompletionSuggestions_rootCommands() throws Exception {
subject.register(literal("foo"));
subject.register(literal("bar"));
subject.register(literal("baz"));
final CommandSuggestions result = subject.getCompletionSuggestions(subject.parse("", source));
assertThat(result.getRange(), equalTo(new StringRange(0, 0)));
assertThat(result.getSuggestions(), equalTo(Lists.newArrayList("bar", "baz", "foo")));
}
@Test
public void getCompletionSuggestions_rootCommands_partial() throws Exception {
subject.register(literal("foo"));
subject.register(literal("bar"));
subject.register(literal("baz"));
final CommandSuggestions result = subject.getCompletionSuggestions(subject.parse("b", source));
assertThat(result.getRange(), equalTo(new StringRange(0, 1)));
assertThat(result.getSuggestions(), equalTo(Lists.newArrayList("bar", "baz")));
}
@Test
public void getCompletionSuggestions_subCommands() throws Exception {
subject.register(
literal("parent")
.then(literal("foo"))
.then(literal("bar"))
.then(literal("baz"))
);
final CommandSuggestions result = subject.getCompletionSuggestions(subject.parse("parent ", source));
assertThat(result.getRange(), equalTo(new StringRange(7, 7)));
assertThat(result.getSuggestions(), equalTo(Lists.newArrayList("bar", "baz", "foo")));
}
@Test
public void getCompletionSuggestions_subCommands_partial() throws Exception {
subject.register(
literal("parent")
.then(literal("foo"))
.then(literal("bar"))
.then(literal("baz"))
);
final CommandSuggestions result = subject.getCompletionSuggestions(subject.parse("parent b", source));
assertThat(result.getRange(), equalTo(new StringRange(7, 8)));
assertThat(result.getSuggestions(), equalTo(Lists.newArrayList("bar", "baz")));
}
}