New suggestion API

This commit is contained in:
Nathan Adams
2017-11-20 13:45:05 +01:00
parent 557352f289
commit 095498c94e
21 changed files with 494 additions and 150 deletions
@@ -11,6 +11,8 @@ import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
@@ -310,7 +312,7 @@ public class CommandDispatcher<S> {
return self;
}
public CompletableFuture<CommandSuggestions> getCompletionSuggestions(final ParseResults<S> parse) {
public CompletableFuture<Suggestions> getCompletionSuggestions(final ParseResults<S> parse) {
final CommandContextBuilder<S> rootContext = parse.getContext();
final CommandContextBuilder<S> context = rootContext.getLastChild();
final CommandNode<S> parent;
@@ -336,25 +338,23 @@ public class CommandDispatcher<S> {
start = 0;
}
@SuppressWarnings("unchecked") final CompletableFuture<Collection<String>>[] futures = new CompletableFuture[parent.getChildren().size()];
@SuppressWarnings("unchecked") final CompletableFuture<Suggestions>[] futures = new CompletableFuture[parent.getChildren().size()];
int i = 0;
for (final CommandNode<S> node : parent.getChildren()) {
try {
futures[i++] = node.listSuggestions(context.build(parse.getReader().getString()), parse.getReader().getString().substring(start));
futures[i++] = node.listSuggestions(context.build(parse.getReader().getString()), new SuggestionsBuilder(parse.getReader().getString(), start));
} catch (final CommandSyntaxException e) {
futures[i++] = CompletableFuture.completedFuture(Collections.emptyList());
futures[i++] = Suggestions.empty();
}
}
final CompletableFuture<CommandSuggestions> result = new CompletableFuture<>();
final CompletableFuture<Suggestions> result = new CompletableFuture<>();
CompletableFuture.allOf(futures).thenRun(() -> {
final Set<String> suggestions = Sets.newHashSet();
for (final CompletableFuture<Collection<String>> future : futures) {
suggestions.addAll(future.join());
final List<Suggestions> suggestions = Lists.newArrayList();
for (final CompletableFuture<Suggestions> future : futures) {
suggestions.add(future.join());
}
final List<String> sorted = new ArrayList<>(suggestions);
Collections.sort(sorted);
result.complete(new CommandSuggestions(new StringRange(start, parse.getReader().getTotalLength()), sorted));
result.complete(Suggestions.merge(suggestions));
});
return result;
@@ -1,55 +0,0 @@
package com.mojang.brigadier;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
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();
}
@FunctionalInterface
public interface Provider<S> {
CompletableFuture<Collection<String>> getSuggestions(final CommandContext<S> context, final String prefix) throws CommandSyntaxException;
}
}
@@ -4,15 +4,15 @@ import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
public interface ArgumentType<T> {
<S> T parse(StringReader reader, CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException;
default <S> CompletableFuture<Collection<String>> listSuggestions(final CommandContext<S> context, final String command) {
return CompletableFuture.completedFuture(Collections.emptyList());
default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
return Suggestions.empty();
}
}
@@ -1,13 +1,12 @@
package com.mojang.brigadier.arguments;
import com.google.common.collect.Lists;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class BoolArgumentType implements ArgumentType<Boolean> {
@@ -28,16 +27,13 @@ public class BoolArgumentType implements ArgumentType<Boolean> {
}
@Override
public <S> CompletableFuture<Collection<String>> listSuggestions(final CommandContext<S> context, final String command) {
final List<String> result = Lists.newArrayList();
if ("true".startsWith(command)) {
result.add("true");
public <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
if ("true".startsWith(builder.getRemaining().toLowerCase())) {
builder.suggest("true");
}
if ("false".startsWith(command)) {
result.add("false");
if ("false".startsWith(builder.getRemaining().toLowerCase())) {
builder.suggest("false");
}
return CompletableFuture.completedFuture(result);
return builder.buildFuture();
}
}
@@ -1,14 +1,14 @@
package com.mojang.brigadier.builder;
import com.mojang.brigadier.CommandSuggestions;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode;
public class RequiredArgumentBuilder<S, T> extends ArgumentBuilder<S, RequiredArgumentBuilder<S, T>> {
private final String name;
private final ArgumentType<T> type;
private CommandSuggestions.Provider<S> suggestionsProvider = null;
private SuggestionProvider<S> suggestionsProvider = null;
private RequiredArgumentBuilder(final String name, final ArgumentType<T> type) {
this.name = name;
@@ -19,12 +19,12 @@ public class RequiredArgumentBuilder<S, T> extends ArgumentBuilder<S, RequiredAr
return new RequiredArgumentBuilder<>(name, type);
}
public RequiredArgumentBuilder<S, T> suggests(final CommandSuggestions.Provider<S> provider) {
public RequiredArgumentBuilder<S, T> suggests(final SuggestionProvider<S> provider) {
this.suggestionsProvider = provider;
return getThis();
}
public CommandSuggestions.Provider<S> getSuggestionsProvider() {
public SuggestionProvider<S> getSuggestionsProvider() {
return suggestionsProvider;
}
@@ -0,0 +1,68 @@
package com.mojang.brigadier.suggestion;
import com.mojang.brigadier.context.StringRange;
import java.util.Objects;
public class Suggestion implements Comparable<Suggestion> {
private final StringRange range;
private final String text;
public Suggestion(final StringRange range, final String text) {
this.range = range;
this.text = text;
}
public StringRange getRange() {
return range;
}
public String getText() {
return text;
}
public String apply(final String input) {
if (range.getStart() == 0 && range.getEnd() == input.length()) {
return text;
}
final StringBuilder result = new StringBuilder();
if (range.getStart() > 0) {
result.append(input.substring(0, range.getStart()));
}
result.append(text);
if (range.getEnd() < input.length()) {
result.append(input.substring(range.getEnd()));
}
return result.toString();
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Suggestion)) {
return false;
}
final Suggestion that = (Suggestion) o;
return Objects.equals(range, that.range) && Objects.equals(text, that.text);
}
@Override
public int hashCode() {
return Objects.hash(range, text);
}
@Override
public String toString() {
return "Suggestion{" +
"range=" + range +
", text='" + text + '\'' +
'}';
}
@Override
public int compareTo(final Suggestion o) {
return text.compareTo(o.text);
}
}
@@ -0,0 +1,11 @@
package com.mojang.brigadier.suggestion;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.concurrent.CompletableFuture;
@FunctionalInterface
public interface SuggestionProvider<S> {
CompletableFuture<Suggestions> getSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) throws CommandSyntaxException;
}
@@ -0,0 +1,92 @@
package com.mojang.brigadier.suggestion;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.mojang.brigadier.context.StringRange;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
public class Suggestions {
private static final Suggestions EMPTY = new Suggestions("", Lists.newArrayList());
private final String input;
private final StringRange range;
private final List<Suggestion> suggestions;
public Suggestions(final String input, final List<Suggestion> suggestions) {
this.input = input;
this.suggestions = suggestions;
if (suggestions.isEmpty()) {
range = new StringRange(input.length(), input.length());
} else {
int start = Integer.MAX_VALUE;
int end = Integer.MIN_VALUE;
for (final Suggestion suggestion : suggestions) {
start = Math.min(start, suggestion.getRange().getStart());
end = Math.max(end, suggestion.getRange().getEnd());
}
range = new StringRange(start, end);
}
}
public String getInput() {
return input;
}
public StringRange getRange() {
return range;
}
public List<Suggestion> getList() {
return suggestions;
}
public boolean isEmpty() {
return suggestions.isEmpty();
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Suggestions)) {
return false;
}
final Suggestions that = (Suggestions) o;
return Objects.equals(input, that.input) &&
Objects.equals(range, that.range) &&
Objects.equals(suggestions, that.suggestions);
}
@Override
public int hashCode() {
return Objects.hash(input, range, suggestions);
}
public static CompletableFuture<Suggestions> empty() {
return CompletableFuture.completedFuture(EMPTY);
}
public static Suggestions merge(final Collection<Suggestions> inputs) {
if (inputs.isEmpty()) {
return EMPTY;
} else if (inputs.size() == 1) {
return inputs.iterator().next();
}
final Set<Suggestion> suggestions = Sets.newHashSet();
for (final Suggestions input : inputs) {
suggestions.addAll(input.getList());
}
final List<Suggestion> sorted = Lists.newArrayList(suggestions);
Collections.sort(sorted);
return new Suggestions(inputs.iterator().next().getInput(), sorted);
}
}
@@ -0,0 +1,54 @@
package com.mojang.brigadier.suggestion;
import com.google.common.base.Strings;
import com.mojang.brigadier.context.StringRange;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class SuggestionsBuilder {
private final String input;
private final int start;
private final String remaining;
private final List<Suggestion> result = new ArrayList<>();
public SuggestionsBuilder(final String input, final int start) {
this.input = input;
this.start = start;
this.remaining = input.substring(start);
}
public String getInput() {
return input;
}
public int getStart() {
return start;
}
public String getRemaining() {
return remaining;
}
public Suggestions build() {
return new Suggestions(input, result);
}
public CompletableFuture<Suggestions> buildFuture() {
return CompletableFuture.completedFuture(new Suggestions(input, result));
}
public SuggestionsBuilder suggest(final String text) {
if (text.equals(remaining)) {
return this;
}
final String prefix = Strings.commonPrefix(text, remaining);
result.add(new Suggestion(new StringRange(start + prefix.length(), input.length()), text.substring(prefix.length())));
return this;
}
public SuggestionsBuilder restart() {
return new SuggestionsBuilder(input, start);
}
}
@@ -1,7 +1,6 @@
package com.mojang.brigadier.tree;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandSuggestions;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
@@ -10,8 +9,10 @@ import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedArgument;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
@@ -21,9 +22,9 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
private final String name;
private final ArgumentType<T> type;
private final CommandSuggestions.Provider<S> customSuggestions;
private final SuggestionProvider<S> customSuggestions;
public ArgumentCommandNode(final String name, final ArgumentType<T> type, final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final CommandSuggestions.Provider<S> customSuggestions) {
public ArgumentCommandNode(final String name, final ArgumentType<T> type, final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final SuggestionProvider<S> customSuggestions) {
super(command, requirement, redirect, modifier);
this.name = name;
this.type = type;
@@ -44,7 +45,7 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
return USAGE_ARGUMENT_OPEN + name + USAGE_ARGUMENT_CLOSE;
}
public CommandSuggestions.Provider<S> getCustomSuggestions() {
public SuggestionProvider<S> getCustomSuggestions() {
return customSuggestions;
}
@@ -59,11 +60,11 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
}
@Override
public CompletableFuture<Collection<String>> listSuggestions(final CommandContext<S> context, final String command) throws CommandSyntaxException {
public CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) throws CommandSyntaxException {
if (customSuggestions == null) {
return type.listSuggestions(context, command);
return type.listSuggestions(context, builder);
} else {
return customSuggestions.getSuggestions(context, command);
return customSuggestions.getSuggestions(context, builder);
}
}
@@ -9,6 +9,8 @@ import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.LinkedHashMap;
@@ -104,7 +106,7 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
public abstract void parse(StringReader reader, CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException;
public abstract CompletableFuture<Collection<String>> listSuggestions(CommandContext<S> context, String command) throws CommandSyntaxException;
public abstract CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) throws CommandSyntaxException;
public abstract ArgumentBuilder<S, ?> createBuilder();
@@ -9,9 +9,9 @@ import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
@@ -50,11 +50,11 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
}
@Override
public CompletableFuture<Collection<String>> listSuggestions(final CommandContext<S> context, final String command) {
if (literal.toLowerCase().startsWith(command.toLowerCase())) {
return CompletableFuture.completedFuture(Collections.singleton(literal));
public CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
if (literal.toLowerCase().startsWith(builder.getRemaining().toLowerCase())) {
return builder.suggest(literal).buildFuture();
} else {
return CompletableFuture.completedFuture(Collections.emptyList());
return Suggestions.empty();
}
}
@@ -5,8 +5,9 @@ import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
@@ -30,8 +31,8 @@ public class RootCommandNode<S> extends CommandNode<S> {
}
@Override
public CompletableFuture<Collection<String>> listSuggestions(CommandContext<S> context, final String command) {
return CompletableFuture.completedFuture(Collections.emptyList());
public CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
return Suggestions.empty();
}
@Override