From b48dbe791688f42c4ef73624bdc4aa7f03a907b0 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Wed, 8 Nov 2017 12:43:07 +0100 Subject: [PATCH] Reimplemented command suggestions --- build.gradle | 2 +- .../mojang/brigadier/CommandDispatcher.java | 65 +++++++-------- .../mojang/brigadier/CommandSuggestions.java | 45 +++++++++++ .../mojang/brigadier/context/StringRange.java | 8 ++ .../CommandDispatcherCompletionsTest.java | 64 --------------- .../brigadier/CommandSuggestionsTest.java | 79 +++++++++++++++++++ 6 files changed, 163 insertions(+), 100 deletions(-) create mode 100644 src/main/java/com/mojang/brigadier/CommandSuggestions.java delete mode 100644 src/test/java/com/mojang/brigadier/CommandDispatcherCompletionsTest.java create mode 100644 src/test/java/com/mojang/brigadier/CommandSuggestionsTest.java diff --git a/build.gradle b/build.gradle index 1a372e0..0f838ab 100644 --- a/build.gradle +++ b/build.gradle @@ -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) { diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java index 2d2f7ad..2d28761 100644 --- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java +++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java @@ -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 { return self; } - private Set findSuggestions(final CommandNode node, final StringReader reader, final CommandContextBuilder contextBuilder, final Set result) { - if (node.getRedirect() != null) { - return findSuggestions(node.getRedirect(), reader, contextBuilder, result); - } - final S source = contextBuilder.getSource(); - for (final CommandNode child : node.getChildren()) { - if (!child.canUse(source)) { - continue; - } - final CommandContextBuilder 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 parse) { + final CommandContextBuilder context = parse.getContext(); + + final Set suggestions = new LinkedHashSet<>(); + + final CommandNode parent; + final int start; + + if (context.getNodes().isEmpty()) { + parent = root; + start = 0; + } else if (parse.getReader().canRead()) { + final Map.Entry, StringRange> entry = Iterables.getLast(context.getNodes().entrySet()); + parent = entry.getKey(); + start = entry.getValue().getEnd() + 1; + } else if (context.getNodes().size() > 1) { + final Map.Entry, 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 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 nodes = findSuggestions(root, reader, new CommandContextBuilder<>(this, source, 0), Sets.newLinkedHashSet()); - - return nodes.toArray(new String[nodes.size()]); + final List result = new ArrayList<>(suggestions); + Collections.sort(result); + return new CommandSuggestions(new StringRange(start, parse.getReader().getTotalLength()), result); } public RootCommandNode getRoot() { diff --git a/src/main/java/com/mojang/brigadier/CommandSuggestions.java b/src/main/java/com/mojang/brigadier/CommandSuggestions.java new file mode 100644 index 0000000..24ee52d --- /dev/null +++ b/src/main/java/com/mojang/brigadier/CommandSuggestions.java @@ -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 suggestions; + + public CommandSuggestions(final StringRange range, final List suggestions) { + this.range = range; + this.suggestions = suggestions; + } + + public StringRange getRange() { + return range; + } + + public List 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(); + } +} diff --git a/src/main/java/com/mojang/brigadier/context/StringRange.java b/src/main/java/com/mojang/brigadier/context/StringRange.java index 5d47c1f..f97cdcb 100644 --- a/src/main/java/com/mojang/brigadier/context/StringRange.java +++ b/src/main/java/com/mojang/brigadier/context/StringRange.java @@ -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 + + '}'; + } } diff --git a/src/test/java/com/mojang/brigadier/CommandDispatcherCompletionsTest.java b/src/test/java/com/mojang/brigadier/CommandDispatcherCompletionsTest.java deleted file mode 100644 index 640a6e4..0000000 --- a/src/test/java/com/mojang/brigadier/CommandDispatcherCompletionsTest.java +++ /dev/null @@ -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 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())); - } -} \ No newline at end of file diff --git a/src/test/java/com/mojang/brigadier/CommandSuggestionsTest.java b/src/test/java/com/mojang/brigadier/CommandSuggestionsTest.java new file mode 100644 index 0000000..e0eaf67 --- /dev/null +++ b/src/test/java/com/mojang/brigadier/CommandSuggestionsTest.java @@ -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 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"))); + } +} \ No newline at end of file