Implement calculation of suggestions for any text position
This commit is contained in:
parent
b10b4479e2
commit
107b852c74
13 changed files with 277 additions and 103 deletions
|
@ -3,14 +3,13 @@
|
|||
|
||||
package com.mojang.brigadier;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
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.context.SuggestionContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
|
@ -57,6 +56,7 @@ public class CommandDispatcher<S> {
|
|||
private static final String USAGE_OR = "|";
|
||||
|
||||
private final RootCommandNode<S> root;
|
||||
|
||||
private final Predicate<CommandNode<S>> hasCommand = new Predicate<CommandNode<S>>() {
|
||||
@Override
|
||||
public boolean test(final CommandNode<S> input) {
|
||||
|
@ -231,7 +231,7 @@ public class CommandDispatcher<S> {
|
|||
final CommandContext<S> child = context.getChild();
|
||||
if (child != null) {
|
||||
forked |= context.isForked();
|
||||
if (!child.getNodes().isEmpty()) {
|
||||
if (child.hasNodes()) {
|
||||
foundCommand = true;
|
||||
final RedirectModifier<S> modifier = context.getRedirectModifier();
|
||||
if (modifier == null) {
|
||||
|
@ -345,24 +345,14 @@ public class CommandDispatcher<S> {
|
|||
* @see #execute(String, Object)
|
||||
*/
|
||||
public ParseResults<S> parse(final StringReader command, final S source) {
|
||||
final CommandContextBuilder<S> context = new CommandContextBuilder<>(this, source, 0);
|
||||
final CommandContextBuilder<S> context = new CommandContextBuilder<>(this, source, root, command.getCursor());
|
||||
return parseNodes(root, command, context);
|
||||
}
|
||||
|
||||
private static class PartialParse<S> {
|
||||
public final CommandContextBuilder<S> context;
|
||||
public final ParseResults<S> parse;
|
||||
|
||||
private PartialParse(final CommandContextBuilder<S> context, final ParseResults<S> parse) {
|
||||
this.context = context;
|
||||
this.parse = parse;
|
||||
}
|
||||
}
|
||||
|
||||
private ParseResults<S> parseNodes(final CommandNode<S> node, final StringReader originalReader, final CommandContextBuilder<S> contextSoFar) {
|
||||
final S source = contextSoFar.getSource();
|
||||
Map<CommandNode<S>, CommandSyntaxException> errors = null;
|
||||
List<PartialParse<S>> potentials = null;
|
||||
List<ParseResults<S>> potentials = null;
|
||||
final int cursor = originalReader.getCursor();
|
||||
|
||||
for (final CommandNode<S> child : node.getRelevantNodes(originalReader)) {
|
||||
|
@ -395,48 +385,47 @@ public class CommandDispatcher<S> {
|
|||
if (reader.canRead(child.getRedirect() == null ? 2 : 1)) {
|
||||
reader.skip();
|
||||
if (child.getRedirect() != null) {
|
||||
final CommandContextBuilder<S> childContext = new CommandContextBuilder<>(this, source, reader.getCursor());
|
||||
childContext.withNode(child.getRedirect(), StringRange.between(cursor, reader.getCursor() - 1));
|
||||
final CommandContextBuilder<S> childContext = new CommandContextBuilder<>(this, source, child.getRedirect(), reader.getCursor());
|
||||
final ParseResults<S> parse = parseNodes(child.getRedirect(), reader, childContext);
|
||||
context.withChild(parse.getContext());
|
||||
return new ParseResults<>(context, originalReader.getCursor(), parse.getReader(), parse.getExceptions());
|
||||
return new ParseResults<>(context, parse.getReader(), parse.getExceptions());
|
||||
} else {
|
||||
final ParseResults<S> parse = parseNodes(child, reader, context);
|
||||
if (potentials == null) {
|
||||
potentials = new ArrayList<>(1);
|
||||
}
|
||||
potentials.add(new PartialParse<>(context, parse));
|
||||
potentials.add(parse);
|
||||
}
|
||||
} else {
|
||||
if (potentials == null) {
|
||||
potentials = new ArrayList<>(1);
|
||||
}
|
||||
potentials.add(new PartialParse<>(context, new ParseResults<>(context, originalReader.getCursor(), reader, Collections.emptyMap())));
|
||||
potentials.add(new ParseResults<>(context, reader, Collections.emptyMap()));
|
||||
}
|
||||
}
|
||||
|
||||
if (potentials != null) {
|
||||
if (potentials.size() > 1) {
|
||||
potentials.sort((a, b) -> {
|
||||
if (!a.parse.getReader().canRead() && b.parse.getReader().canRead()) {
|
||||
if (!a.getReader().canRead() && b.getReader().canRead()) {
|
||||
return -1;
|
||||
}
|
||||
if (a.parse.getReader().canRead() && !b.parse.getReader().canRead()) {
|
||||
if (a.getReader().canRead() && !b.getReader().canRead()) {
|
||||
return 1;
|
||||
}
|
||||
if (a.parse.getExceptions().isEmpty() && !b.parse.getExceptions().isEmpty()) {
|
||||
if (a.getExceptions().isEmpty() && !b.getExceptions().isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
if (!a.parse.getExceptions().isEmpty() && b.parse.getExceptions().isEmpty()) {
|
||||
if (!a.getExceptions().isEmpty() && b.getExceptions().isEmpty()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
return potentials.get(0).parse;
|
||||
return potentials.get(0);
|
||||
}
|
||||
|
||||
return new ParseResults<>(contextSoFar, originalReader.getCursor(), originalReader, errors == null ? Collections.emptyMap() : errors);
|
||||
return new ParseResults<>(contextSoFar, originalReader, errors == null ? Collections.emptyMap() : errors);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -589,37 +578,24 @@ public class CommandDispatcher<S> {
|
|||
* @return a future that will eventually resolve into a {@link Suggestions} object
|
||||
*/
|
||||
public CompletableFuture<Suggestions> getCompletionSuggestions(final ParseResults<S> parse) {
|
||||
final CommandContextBuilder<S> rootContext = parse.getContext();
|
||||
final CommandContextBuilder<S> context = rootContext.getLastChild();
|
||||
final CommandNode<S> parent;
|
||||
final int start;
|
||||
return getCompletionSuggestions(parse, parse.getReader().getTotalLength());
|
||||
}
|
||||
|
||||
if (context.getNodes().isEmpty()) {
|
||||
parent = root;
|
||||
start = parse.getStartIndex();
|
||||
} 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 if (rootContext != context && context.getNodes().size() > 0) {
|
||||
final Map.Entry<CommandNode<S>, StringRange> entry = Iterables.getLast(context.getNodes().entrySet());
|
||||
parent = entry.getKey();
|
||||
start = entry.getValue().getEnd() + 1;
|
||||
} else {
|
||||
parent = root;
|
||||
start = parse.getStartIndex();
|
||||
}
|
||||
public CompletableFuture<Suggestions> getCompletionSuggestions(final ParseResults<S> parse, int cursor) {
|
||||
final CommandContextBuilder<S> context = parse.getContext();
|
||||
|
||||
final SuggestionContext<S> nodeBeforeCursor = context.findSuggestionContext(cursor);
|
||||
final CommandNode<S> parent = nodeBeforeCursor.parent;
|
||||
final int start = Math.min(nodeBeforeCursor.startPos, cursor);
|
||||
|
||||
final String fullInput = parse.getReader().getString();
|
||||
final String truncatedInput = fullInput.substring(0, cursor);
|
||||
@SuppressWarnings("unchecked") final CompletableFuture<Suggestions>[] futures = new CompletableFuture[parent.getChildren().size()];
|
||||
int i = 0;
|
||||
for (final CommandNode<S> node : parent.getChildren()) {
|
||||
CompletableFuture<Suggestions> future = Suggestions.empty();
|
||||
try {
|
||||
future = node.listSuggestions(context.build(parse.getReader().getString()), new SuggestionsBuilder(parse.getReader().getString(), start));
|
||||
future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start));
|
||||
} catch (final CommandSyntaxException ignored) {
|
||||
}
|
||||
futures[i++] = future;
|
||||
|
@ -631,7 +607,7 @@ public class CommandDispatcher<S> {
|
|||
for (final CompletableFuture<Suggestions> future : futures) {
|
||||
suggestions.add(future.join());
|
||||
}
|
||||
result.complete(Suggestions.merge(parse.getReader().getString(), suggestions));
|
||||
result.complete(Suggestions.merge(fullInput, suggestions));
|
||||
});
|
||||
|
||||
return result;
|
||||
|
|
|
@ -13,22 +13,16 @@ import java.util.Map;
|
|||
public class ParseResults<S> {
|
||||
private final CommandContextBuilder<S> context;
|
||||
private final Map<CommandNode<S>, CommandSyntaxException> exceptions;
|
||||
private final int startIndex;
|
||||
private final ImmutableStringReader reader;
|
||||
|
||||
public ParseResults(final CommandContextBuilder<S> context, final int startIndex, final ImmutableStringReader reader, final Map<CommandNode<S>, CommandSyntaxException> exceptions) {
|
||||
public ParseResults(final CommandContextBuilder<S> context, final ImmutableStringReader reader, final Map<CommandNode<S>, CommandSyntaxException> exceptions) {
|
||||
this.context = context;
|
||||
this.startIndex = startIndex;
|
||||
this.reader = reader;
|
||||
this.exceptions = exceptions;
|
||||
}
|
||||
|
||||
public ParseResults(final CommandContextBuilder<S> context) {
|
||||
this(context, 0, new StringReader(""), Collections.emptyMap());
|
||||
}
|
||||
|
||||
public int getStartIndex() {
|
||||
return startIndex;
|
||||
this(context, new StringReader(""), Collections.emptyMap());
|
||||
}
|
||||
|
||||
public CommandContextBuilder<S> getContext() {
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.mojang.brigadier.Command;
|
|||
import com.mojang.brigadier.RedirectModifier;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CommandContext<S> {
|
||||
|
@ -16,17 +17,19 @@ public class CommandContext<S> {
|
|||
private final String input;
|
||||
private final Command<S> command;
|
||||
private final Map<String, ParsedArgument<S, ?>> arguments;
|
||||
private final Map<CommandNode<S>, StringRange> nodes;
|
||||
private final CommandNode<S> rootNode;
|
||||
private final List<ParsedCommandNode<S>> nodes;
|
||||
private final StringRange range;
|
||||
private final CommandContext<S> child;
|
||||
private final RedirectModifier<S> modifier;
|
||||
private final boolean forks;
|
||||
|
||||
public CommandContext(final S source, final String input, final Map<String, ParsedArgument<S, ?>> arguments, final Command<S> command, final Map<CommandNode<S>, StringRange> nodes, final StringRange range, final CommandContext<S> child, final RedirectModifier<S> modifier, boolean forks) {
|
||||
public CommandContext(final S source, final String input, final Map<String, ParsedArgument<S, ?>> arguments, final Command<S> command, final CommandNode<S> rootNode, final List<ParsedCommandNode<S>> nodes, final StringRange range, final CommandContext<S> child, final RedirectModifier<S> modifier, boolean forks) {
|
||||
this.source = source;
|
||||
this.input = input;
|
||||
this.arguments = arguments;
|
||||
this.command = command;
|
||||
this.rootNode = rootNode;
|
||||
this.nodes = nodes;
|
||||
this.range = range;
|
||||
this.child = child;
|
||||
|
@ -38,7 +41,7 @@ public class CommandContext<S> {
|
|||
if (this.source == source) {
|
||||
return this;
|
||||
}
|
||||
return new CommandContext<>(source, input, arguments, command, nodes, range, child, modifier, forks);
|
||||
return new CommandContext<>(source, input, arguments, command, rootNode, nodes, range, child, modifier, forks);
|
||||
}
|
||||
|
||||
public CommandContext<S> getChild() {
|
||||
|
@ -85,7 +88,8 @@ public class CommandContext<S> {
|
|||
final CommandContext that = (CommandContext) o;
|
||||
|
||||
if (!arguments.equals(that.arguments)) return false;
|
||||
if (!Iterables.elementsEqual(nodes.entrySet(), that.nodes.entrySet())) return false;
|
||||
if (!rootNode.equals(that.rootNode)) return false;
|
||||
if (!Iterables.elementsEqual(nodes, that.nodes)) return false;
|
||||
if (command != null ? !command.equals(that.command) : that.command != null) return false;
|
||||
if (!source.equals(that.source)) return false;
|
||||
if (child != null ? !child.equals(that.child) : that.child != null) return false;
|
||||
|
@ -98,6 +102,7 @@ public class CommandContext<S> {
|
|||
int result = source.hashCode();
|
||||
result = 31 * result + arguments.hashCode();
|
||||
result = 31 * result + (command != null ? command.hashCode() : 0);
|
||||
result = 31 * result + rootNode.hashCode();
|
||||
result = 31 * result + nodes.hashCode();
|
||||
result = 31 * result + (child != null ? child.hashCode() : 0);
|
||||
return result;
|
||||
|
@ -115,10 +120,18 @@ public class CommandContext<S> {
|
|||
return input;
|
||||
}
|
||||
|
||||
public Map<CommandNode<S>, StringRange> getNodes() {
|
||||
public CommandNode<S> getRootNode() {
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
public List<ParsedCommandNode<S>> getNodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public boolean hasNodes() {
|
||||
return !nodes.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isForked() {
|
||||
return forks;
|
||||
}
|
||||
|
|
|
@ -3,17 +3,21 @@
|
|||
|
||||
package com.mojang.brigadier.context;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.RedirectModifier;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CommandContextBuilder<S> {
|
||||
private final Map<String, ParsedArgument<S, ?>> arguments = Maps.newLinkedHashMap();
|
||||
private final Map<CommandNode<S>, StringRange> nodes = Maps.newLinkedHashMap();
|
||||
private final CommandNode<S> rootNode;
|
||||
private final List<ParsedCommandNode<S>> nodes = Lists.newArrayList();
|
||||
private final CommandDispatcher<S> dispatcher;
|
||||
private S source;
|
||||
private Command<S> command;
|
||||
|
@ -22,7 +26,8 @@ public class CommandContextBuilder<S> {
|
|||
private RedirectModifier<S> modifier = null;
|
||||
private boolean forks;
|
||||
|
||||
public CommandContextBuilder(final CommandDispatcher<S> dispatcher, final S source, final int start) {
|
||||
public CommandContextBuilder(final CommandDispatcher<S> dispatcher, final S source, final CommandNode<S> rootNode, final int start) {
|
||||
this.rootNode = rootNode;
|
||||
this.dispatcher = dispatcher;
|
||||
this.source = source;
|
||||
this.range = StringRange.at(start);
|
||||
|
@ -37,6 +42,10 @@ public class CommandContextBuilder<S> {
|
|||
return source;
|
||||
}
|
||||
|
||||
public CommandNode<S> getRootNode() {
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
public CommandContextBuilder<S> withArgument(final String name, final ParsedArgument<S, ?> argument) {
|
||||
this.arguments.put(name, argument);
|
||||
return this;
|
||||
|
@ -52,7 +61,7 @@ public class CommandContextBuilder<S> {
|
|||
}
|
||||
|
||||
public CommandContextBuilder<S> withNode(final CommandNode<S> node, final StringRange range) {
|
||||
nodes.put(node, range);
|
||||
nodes.add(new ParsedCommandNode<>(node, range));
|
||||
this.range = StringRange.encompassing(this.range, range);
|
||||
this.modifier = node.getRedirectModifier();
|
||||
this.forks = node.isFork();
|
||||
|
@ -60,10 +69,10 @@ public class CommandContextBuilder<S> {
|
|||
}
|
||||
|
||||
public CommandContextBuilder<S> copy() {
|
||||
final CommandContextBuilder<S> copy = new CommandContextBuilder<>(dispatcher, source, range.getStart());
|
||||
final CommandContextBuilder<S> copy = new CommandContextBuilder<>(dispatcher, source, rootNode, range.getStart());
|
||||
copy.command = command;
|
||||
copy.arguments.putAll(arguments);
|
||||
copy.nodes.putAll(nodes);
|
||||
copy.nodes.addAll(nodes);
|
||||
copy.child = child;
|
||||
copy.range = range;
|
||||
copy.forks = forks;
|
||||
|
@ -91,12 +100,12 @@ public class CommandContextBuilder<S> {
|
|||
return command;
|
||||
}
|
||||
|
||||
public Map<CommandNode<S>, StringRange> getNodes() {
|
||||
public List<ParsedCommandNode<S>> getNodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public CommandContext<S> build(final String input) {
|
||||
return new CommandContext<>(source, input, arguments, command, nodes, range, child == null ? null : child.build(input), modifier, forks);
|
||||
return new CommandContext<>(source, input, arguments, command, rootNode, nodes, range, child == null ? null : child.build(input), modifier, forks);
|
||||
}
|
||||
|
||||
public CommandDispatcher<S> getDispatcher() {
|
||||
|
@ -106,4 +115,33 @@ public class CommandContextBuilder<S> {
|
|||
public StringRange getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
public SuggestionContext<S> findSuggestionContext(final int cursor) {
|
||||
if (range.getStart() <= cursor) {
|
||||
if (range.getEnd() < cursor) {
|
||||
if (child != null) {
|
||||
return child.findSuggestionContext(cursor);
|
||||
} else if (!nodes.isEmpty()) {
|
||||
final ParsedCommandNode<S> last = Iterables.getLast(nodes);
|
||||
return new SuggestionContext<>(last.getNode(), last.getRange().getEnd() + 1);
|
||||
} else {
|
||||
return new SuggestionContext<>(rootNode, range.getStart());
|
||||
}
|
||||
} else {
|
||||
CommandNode<S> prev = rootNode;
|
||||
for (final ParsedCommandNode<S> node : nodes) {
|
||||
final StringRange nodeRange = node.getRange();
|
||||
if (nodeRange.getStart() <= cursor && cursor <= nodeRange.getEnd()) {
|
||||
return new SuggestionContext<>(prev, nodeRange.getStart());
|
||||
}
|
||||
prev = node.getNode();
|
||||
}
|
||||
if (prev == null) {
|
||||
throw new IllegalStateException("Can't find node before cursor");
|
||||
}
|
||||
return new SuggestionContext<>(prev, range.getStart());
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Can't find node before cursor");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package com.mojang.brigadier.context;
|
||||
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ParsedCommandNode<S> {
|
||||
|
||||
private final CommandNode<S> node;
|
||||
|
||||
private final StringRange range;
|
||||
|
||||
public ParsedCommandNode(CommandNode<S> node, StringRange range) {
|
||||
this.node = node;
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
public CommandNode<S> getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public StringRange getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return node + "@" + range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ParsedCommandNode<?> that = (ParsedCommandNode<?>) o;
|
||||
return Objects.equals(node, that.node) &&
|
||||
Objects.equals(range, that.range);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(node, range);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package com.mojang.brigadier.context;
|
||||
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
|
||||
public class SuggestionContext<S> {
|
||||
public final CommandNode<S> parent;
|
||||
public final int startPos;
|
||||
|
||||
public SuggestionContext(CommandNode<S> parent, int startPos) {
|
||||
this.parent = parent;
|
||||
this.startPos = startPos;
|
||||
}
|
||||
}
|
|
@ -259,23 +259,33 @@ public class CommandDispatcherTest {
|
|||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testExecuteRedirectedMultipleTimes() throws Exception {
|
||||
subject.register(literal("actual").executes(command));
|
||||
subject.register(literal("redirected").redirect(subject.getRoot()));
|
||||
final LiteralCommandNode<Object> concreteNode = subject.register(literal("actual").executes(command));
|
||||
final LiteralCommandNode<Object> redirectNode = subject.register(literal("redirected").redirect(subject.getRoot()));
|
||||
|
||||
final String input = "redirected redirected actual";
|
||||
|
||||
final ParseResults<Object> parse = subject.parse(input, source);
|
||||
assertThat(parse.getContext().getRange().get(input), equalTo("redirected"));
|
||||
assertThat(parse.getContext().getNodes().size(), is(1));
|
||||
assertThat(parse.getContext().getRootNode(), is(subject.getRoot()));
|
||||
assertThat(parse.getContext().getNodes().get(0).getRange(), equalTo(parse.getContext().getRange()));
|
||||
assertThat(parse.getContext().getNodes().get(0).getNode(), is(redirectNode));
|
||||
|
||||
final CommandContextBuilder<Object> child1 = parse.getContext().getChild();
|
||||
assertThat(child1, is(notNullValue()));
|
||||
assertThat(child1.getRange().get(input), equalTo("redirected redirected"));
|
||||
assertThat(child1.getNodes().size(), is(2));
|
||||
assertThat(child1.getRange().get(input), equalTo("redirected"));
|
||||
assertThat(child1.getNodes().size(), is(1));
|
||||
assertThat(child1.getRootNode(), is(subject.getRoot()));
|
||||
assertThat(child1.getNodes().get(0).getRange(), equalTo(child1.getRange()));
|
||||
assertThat(child1.getNodes().get(0).getNode(), is(redirectNode));
|
||||
|
||||
final CommandContextBuilder<Object> child2 = child1.getChild();
|
||||
assertThat(child2, is(notNullValue()));
|
||||
assertThat(child2.getRange().get(input), equalTo("redirected actual"));
|
||||
assertThat(child2.getNodes().size(), is(2));
|
||||
assertThat(child2.getRange().get(input), equalTo("actual"));
|
||||
assertThat(child2.getNodes().size(), is(1));
|
||||
assertThat(child2.getRootNode(), is(subject.getRoot()));
|
||||
assertThat(child2.getNodes().get(0).getRange(), equalTo(child2.getRange()));
|
||||
assertThat(child2.getNodes().get(0).getNode(), is(concreteNode));
|
||||
|
||||
assertThat(subject.execute(parse), is(42));
|
||||
verify(command).run(any(CommandContext.class));
|
||||
|
@ -290,19 +300,25 @@ public class CommandDispatcherTest {
|
|||
|
||||
when(modifier.apply(argThat(hasProperty("source", is(source))))).thenReturn(Lists.newArrayList(source1, source2));
|
||||
|
||||
subject.register(literal("actual").executes(command));
|
||||
subject.register(literal("redirected").fork(subject.getRoot(), modifier));
|
||||
final LiteralCommandNode<Object> concreteNode = subject.register(literal("actual").executes(command));
|
||||
final LiteralCommandNode<Object> redirectNode = subject.register(literal("redirected").fork(subject.getRoot(), modifier));
|
||||
|
||||
final String input = "redirected actual";
|
||||
final ParseResults<Object> parse = subject.parse(input, source);
|
||||
assertThat(parse.getContext().getRange().get(input), equalTo("redirected"));
|
||||
assertThat(parse.getContext().getNodes().size(), is(1));
|
||||
assertThat(parse.getContext().getRootNode(), equalTo(subject.getRoot()));
|
||||
assertThat(parse.getContext().getNodes().get(0).getRange(), equalTo(parse.getContext().getRange()));
|
||||
assertThat(parse.getContext().getNodes().get(0).getNode(), is(redirectNode));
|
||||
assertThat(parse.getContext().getSource(), is(source));
|
||||
|
||||
final CommandContextBuilder<Object> parent = parse.getContext().getChild();
|
||||
assertThat(parent, is(notNullValue()));
|
||||
assertThat(parent.getRange().get(input), equalTo("redirected actual"));
|
||||
assertThat(parent.getNodes().size(), is(2));
|
||||
assertThat(parent.getRange().get(input), equalTo("actual"));
|
||||
assertThat(parent.getNodes().size(), is(1));
|
||||
assertThat(parse.getContext().getRootNode(), equalTo(subject.getRoot()));
|
||||
assertThat(parent.getNodes().get(0).getRange(), equalTo(parent.getRange()));
|
||||
assertThat(parent.getNodes().get(0).getNode(), is(concreteNode));
|
||||
assertThat(parent.getSource(), is(source));
|
||||
|
||||
assertThat(subject.execute(parse), is(2));
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package com.mojang.brigadier;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
|
@ -102,11 +103,11 @@ public class CommandDispatcherUsagesTest {
|
|||
}
|
||||
|
||||
private CommandNode<Object> get(final String command) {
|
||||
return Iterators.getLast(subject.parse(command, source).getContext().getNodes().keySet().iterator());
|
||||
return Iterables.getLast(subject.parse(command, source).getContext().getNodes()).getNode();
|
||||
}
|
||||
|
||||
private CommandNode<Object> get(final StringReader command) {
|
||||
return Iterators.getLast(subject.parse(command, source).getContext().getNodes().keySet().iterator());
|
||||
return Iterables.getLast(subject.parse(command, source).getContext().getNodes()).getNode();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -189,10 +190,10 @@ public class CommandDispatcherUsagesTest {
|
|||
|
||||
final Map<CommandNode<Object>, String> results = subject.getSmartUsage(get(offsetH), source);
|
||||
assertThat(results, equalTo(ImmutableMap.builder()
|
||||
.put(get("h 1"), "[1] i")
|
||||
.put(get("h 2"), "[2] i ii")
|
||||
.put(get("h 3"), "[3]")
|
||||
.build()
|
||||
.put(get("h 1"), "[1] i")
|
||||
.put(get("h 2"), "[2] i ii")
|
||||
.put(get("h 3"), "[3]")
|
||||
.build()
|
||||
));
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ import org.junit.runner.RunWith;
|
|||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
|
||||
import static com.mojang.brigadier.arguments.StringArgumentType.word;
|
||||
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
|
||||
|
@ -33,6 +35,18 @@ public class CommandSuggestionsTest {
|
|||
subject = new CommandDispatcher<>();
|
||||
}
|
||||
|
||||
private void testSuggestions(final String contents, final int cursor, final StringRange range, final String... suggestions) {
|
||||
final Suggestions result = subject.getCompletionSuggestions(subject.parse(contents, source), cursor).join();
|
||||
assertThat(result.getRange(), equalTo(range));
|
||||
|
||||
final List<Suggestion> expected = Lists.newArrayList();
|
||||
for (final String suggestion : suggestions) {
|
||||
expected.add(new Suggestion(range, suggestion));
|
||||
}
|
||||
|
||||
assertThat(result.getList(), equalTo(expected));
|
||||
}
|
||||
|
||||
private static StringReader inputWithOffset(final String input, final int offset) {
|
||||
final StringReader result = new StringReader(input);
|
||||
result.setCursor(offset);
|
||||
|
@ -102,6 +116,31 @@ public class CommandSuggestionsTest {
|
|||
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.at(7), "bar"), new Suggestion(StringRange.at(7), "baz"), new Suggestion(StringRange.at(7), "foo"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCompletionSuggestions_movingCursor_subCommands() throws Exception {
|
||||
subject.register(
|
||||
literal("parent_one")
|
||||
.then(literal("faz"))
|
||||
.then(literal("fbz"))
|
||||
.then(literal("gaz"))
|
||||
);
|
||||
|
||||
subject.register(
|
||||
literal("parent_two")
|
||||
);
|
||||
|
||||
testSuggestions("parent_one faz ", 0, StringRange.at(0), "parent_one", "parent_two");
|
||||
testSuggestions("parent_one faz ", 1, StringRange.between(0, 1), "parent_one", "parent_two");
|
||||
testSuggestions("parent_one faz ", 7, StringRange.between(0, 7), "parent_one", "parent_two");
|
||||
testSuggestions("parent_one faz ", 8, StringRange.between(0, 8), "parent_one");
|
||||
testSuggestions("parent_one faz ", 10, StringRange.at(0));
|
||||
testSuggestions("parent_one faz ", 11, StringRange.at(11), "faz", "fbz", "gaz");
|
||||
testSuggestions("parent_one faz ", 12, StringRange.between(11, 12), "faz", "fbz");
|
||||
testSuggestions("parent_one faz ", 13, StringRange.between(11, 13), "faz");
|
||||
testSuggestions("parent_one faz ", 14, StringRange.at(0));
|
||||
testSuggestions("parent_one faz ", 15, StringRange.at(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCompletionSuggestions_subCommands_partial() throws Exception {
|
||||
subject.register(
|
||||
|
@ -121,10 +160,10 @@ public class CommandSuggestionsTest {
|
|||
@Test
|
||||
public void getCompletionSuggestions_subCommands_partial_withInputOffset() throws Exception {
|
||||
subject.register(
|
||||
literal("parent")
|
||||
.then(literal("foo"))
|
||||
.then(literal("bar"))
|
||||
.then(literal("baz"))
|
||||
literal("parent")
|
||||
.then(literal("foo"))
|
||||
.then(literal("bar"))
|
||||
.then(literal("baz"))
|
||||
);
|
||||
|
||||
final ParseResults<Object> parse = subject.parse(inputWithOffset("junk parent b", 5), source);
|
||||
|
@ -158,6 +197,29 @@ public class CommandSuggestionsTest {
|
|||
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(9, 10), "sub"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCompletionSuggestions_movingCursor_redirect() throws Exception {
|
||||
final LiteralCommandNode<Object> actualOne = subject.register(literal("actual_one")
|
||||
.then(literal("faz"))
|
||||
.then(literal("fbz"))
|
||||
.then(literal("gaz"))
|
||||
);
|
||||
|
||||
final LiteralCommandNode<Object> actualTwo = subject.register(literal("actual_two"));
|
||||
|
||||
subject.register(literal("redirect_one").redirect(actualOne));
|
||||
subject.register(literal("redirect_two").redirect(actualOne));
|
||||
|
||||
testSuggestions("redirect_one faz ", 0, StringRange.at(0), "actual_one", "actual_two", "redirect_one", "redirect_two");
|
||||
testSuggestions("redirect_one faz ", 9, StringRange.between(0, 9), "redirect_one", "redirect_two");
|
||||
testSuggestions("redirect_one faz ", 10, StringRange.between(0, 10), "redirect_one");
|
||||
testSuggestions("redirect_one faz ", 12, StringRange.at(0));
|
||||
testSuggestions("redirect_one faz ", 13, StringRange.at(13), "faz", "fbz", "gaz");
|
||||
testSuggestions("redirect_one faz ", 14, StringRange.between(13, 14), "faz", "fbz");
|
||||
testSuggestions("redirect_one faz ", 15, StringRange.between(13, 15), "faz");
|
||||
testSuggestions("redirect_one faz ", 16, StringRange.at(0));
|
||||
testSuggestions("redirect_one faz ", 17, StringRange.at(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCompletionSuggestions_redirectPartial_withInputOffset() throws Exception {
|
||||
|
|
|
@ -7,13 +7,13 @@ import com.google.common.testing.EqualsTester;
|
|||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.RootCommandNode;
|
||||
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 org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
@ -26,9 +26,12 @@ public class CommandContextTest {
|
|||
@Mock
|
||||
private CommandDispatcher<Object> dispatcher;
|
||||
|
||||
@Mock
|
||||
private CommandNode<Object> rootNode;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
builder = new CommandContextBuilder<>(dispatcher, source, 0);
|
||||
builder = new CommandContextBuilder<>(dispatcher, source, rootNode, 0);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
|
@ -53,22 +56,30 @@ public class CommandContextTest {
|
|||
assertThat(builder.build("").getSource(), is(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRootNode() throws Exception {
|
||||
assertThat(builder.build("").getRootNode(), is(rootNode));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testEquals() throws Exception {
|
||||
final Object otherSource = new Object();
|
||||
final Command<Object> command = mock(Command.class);
|
||||
final Command<Object> otherCommand = mock(Command.class);
|
||||
final CommandNode<Object> rootNode = mock(CommandNode.class);
|
||||
final CommandNode<Object> otherRootNode = mock(CommandNode.class);
|
||||
final CommandNode<Object> node = mock(CommandNode.class);
|
||||
final CommandNode<Object> otherNode = mock(CommandNode.class);
|
||||
new EqualsTester()
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).build(""), new CommandContextBuilder<>(dispatcher, source, 0).build(""))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, otherSource, 0).build(""), new CommandContextBuilder<>(dispatcher, otherSource, 0).build(""))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).withCommand(command).build(""), new CommandContextBuilder<>(dispatcher, source, 0).withCommand(command).build(""))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).withCommand(otherCommand).build(""), new CommandContextBuilder<>(dispatcher, source, 0).withCommand(otherCommand).build(""))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).withArgument("foo", new ParsedArgument<>(0, 1, 123)).build("123"), new CommandContextBuilder<>(dispatcher, source, 0).withArgument("foo", new ParsedArgument<>(0, 1, 123)).build("123"))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).withNode(node, StringRange.between(0, 3)).withNode(otherNode, StringRange.between(4, 6)).build("123 456"), new CommandContextBuilder<>(dispatcher, source, 0).withNode(node, StringRange.between(0, 3)).withNode(otherNode, StringRange.between(4, 6)).build("123 456"))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).withNode(otherNode, StringRange.between(0, 3)).withNode(node, StringRange.between(4, 6)).build("123 456"), new CommandContextBuilder<>(dispatcher, source, 0).withNode(otherNode, StringRange.between(0, 3)).withNode(node, StringRange.between(4, 6)).build("123 456"))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).build(""), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).build(""))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, otherRootNode, 0).build(""), new CommandContextBuilder<>(dispatcher, source, otherRootNode, 0).build(""))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, otherSource, rootNode, 0).build(""), new CommandContextBuilder<>(dispatcher, otherSource, rootNode, 0).build(""))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withCommand(command).build(""), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withCommand(command).build(""))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withCommand(otherCommand).build(""), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withCommand(otherCommand).build(""))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withArgument("foo", new ParsedArgument<>(0, 1, 123)).build("123"), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withArgument("foo", new ParsedArgument<>(0, 1, 123)).build("123"))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withNode(node, StringRange.between(0, 3)).withNode(otherNode, StringRange.between(4, 6)).build("123 456"), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withNode(node, StringRange.between(0, 3)).withNode(otherNode, StringRange.between(4, 6)).build("123 456"))
|
||||
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withNode(otherNode, StringRange.between(0, 3)).withNode(node, StringRange.between(4, 6)).build("123 456"), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withNode(otherNode, StringRange.between(0, 3)).withNode(node, StringRange.between(4, 6)).build("123 456"))
|
||||
.testEquals();
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ public class ArgumentCommandNodeTest extends AbstractCommandNodeTest {
|
|||
@Before
|
||||
public void setUp() throws Exception {
|
||||
node = argument("foo", integer()).build();
|
||||
contextBuilder = new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), 0);
|
||||
contextBuilder = new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), new RootCommandNode<>(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -37,7 +37,7 @@ public class LiteralCommandNodeTest extends AbstractCommandNodeTest {
|
|||
@Before
|
||||
public void setUp() throws Exception {
|
||||
node = literal("foo").build();
|
||||
contextBuilder = new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), 0);
|
||||
contextBuilder = new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), new RootCommandNode<>(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -34,7 +34,7 @@ public class RootCommandNodeTest extends AbstractCommandNodeTest {
|
|||
@Test
|
||||
public void testParse() throws Exception {
|
||||
final StringReader reader = new StringReader("hello world");
|
||||
node.parse(reader, new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), 0));
|
||||
node.parse(reader, new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), new RootCommandNode<>(), 0));
|
||||
assertThat(reader.getCursor(), is(0));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue