Skip to content

Commit

Permalink
Implement calculation of suggestions for any text position
Browse files Browse the repository at this point in the history
  • Loading branch information
boq authored and Dinnerbone committed Sep 26, 2018
1 parent b10b447 commit 107b852
Show file tree
Hide file tree
Showing 13 changed files with 278 additions and 104 deletions.
80 changes: 28 additions & 52 deletions src/main/java/com/mojang/brigadier/CommandDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -231,7 +231,7 @@ public int execute(final ParseResults<S> parse) throws CommandSyntaxException {
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) {
Expand Down Expand Up @@ -345,24 +345,14 @@ public ParseResults<S> parse(final String command, final S source) {
* @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)) {
Expand Down Expand Up @@ -395,48 +385,47 @@ private ParseResults<S> parseNodes(final CommandNode<S> node, final StringReader
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);
}

/**
Expand Down Expand Up @@ -589,37 +578,24 @@ private String getSmartUsage(final CommandNode<S> node, final S source, final bo
* @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;

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();
}
return getCompletionSuggestions(parse, parse.getReader().getTotalLength());
}

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;
Expand All @@ -631,7 +607,7 @@ public CompletableFuture<Suggestions> getCompletionSuggestions(final ParseResult
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;
Expand Down
10 changes: 2 additions & 8 deletions src/main/java/com/mojang/brigadier/ParseResults.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,16 @@
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() {
Expand Down
23 changes: 18 additions & 5 deletions src/main/java/com/mojang/brigadier/context/CommandContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,27 @@
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.tree.CommandNode;

import java.util.List;
import java.util.Map;

public class CommandContext<S> {
private final S source;
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;
Expand All @@ -38,7 +41,7 @@ public CommandContext<S> copyFor(final S source) {
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() {
Expand Down Expand Up @@ -85,7 +88,8 @@ public boolean equals(final Object o) {
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;
Expand All @@ -98,6 +102,7 @@ public int hashCode() {
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;
Expand All @@ -115,10 +120,18 @@ public String getInput() {
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -37,6 +42,10 @@ public S getSource() {
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;
Expand All @@ -52,18 +61,18 @@ public CommandContextBuilder<S> withCommand(final Command<S> command) {
}

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();
return this;
}

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;
Expand Down Expand Up @@ -91,12 +100,12 @@ public Command<S> getCommand() {
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() {
Expand All @@ -106,4 +115,33 @@ public CommandDispatcher<S> getDispatcher() {
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");
}
}
Loading

0 comments on commit 107b852

Please sign in to comment.