Add comprehensive suggestions to many commands.

All patterns now have suggestions, including recursive patterns.
Suggestions will suggest blocks and block states.
All masks now have suggestions, though mask intersections are not
yet supported due to issues with quotes strings.
EntityRemover and ItemFactory now also have completions, as well
as all RegistryConverters (though I am unsure how many are actually
used).

Also use paper's AsyncTabComplete event, if available.
This commit is contained in:
wizjany
2019-05-26 02:20:02 -04:00
parent 871c25e1cd
commit 6962b2e7b6
37 changed files with 463 additions and 64 deletions

View File

@ -61,7 +61,7 @@ public class BlockFactory extends AbstractFactory<BaseBlock> {
public Set<BaseBlock> parseFromListInput(String input, ParserContext context) throws InputParseException {
Set<BaseBlock> blocks = new HashSet<>();
String[] splits = input.split(",");
for (String token : StringUtil.parseListInQuotes(splits, ',', '[', ']')) {
for (String token : StringUtil.parseListInQuotes(splits, ',', '[', ']', true)) {
blocks.add(parseFromInput(token, context));
}
return blocks;

View File

@ -64,13 +64,15 @@ public final class MaskFactory extends AbstractFactory<Mask> {
register(new SolidMaskParser(worldEdit));
register(new LazyRegionMaskParser(worldEdit));
register(new RegionMaskParser(worldEdit));
register(new BlockCategoryMaskParser(worldEdit));
register(new OffsetMaskParser(worldEdit));
register(new BiomeMaskParser(worldEdit));
register(new NoiseMaskParser(worldEdit));
register(new BlockStateMaskParser(worldEdit));
register(new NegateMaskParser(worldEdit));
register(new ExpressionMaskParser(worldEdit));
register(new BlockCategoryMaskParser(worldEdit));
register(new BiomeMaskParser(worldEdit));
register(new BlocksMaskParser(worldEdit));
}

View File

@ -50,10 +50,10 @@ public final class PatternFactory extends AbstractFactory<Pattern> {
register(new RandomPatternParser(worldEdit));
// individual patterns
register(new BlockCategoryPatternParser(worldEdit));
register(new ClipboardPatternParser(worldEdit));
register(new TypeOrStateApplyingPatternParser(worldEdit));
register(new RandomStatePatternParser(worldEdit));
register(new BlockCategoryPatternParser(worldEdit));
// inner-most pattern: just one block - must be last
register(new SingleBlockPatternParser(worldEdit));

View File

@ -27,6 +27,7 @@ import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.MobSpawnerBlock;
import com.sk89q.worldedit.blocks.SignBlock;
import com.sk89q.worldedit.blocks.SkullBlock;
import com.sk89q.worldedit.command.util.SuggestionHelper;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.input.DisallowedUsageException;
import com.sk89q.worldedit.extension.input.InputParseException;
@ -38,7 +39,6 @@ import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.HandSide;
import com.sk89q.worldedit.util.formatting.component.ErrorFormat;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
@ -101,7 +101,7 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
}
}
private static String[] EMPTY_STRING_ARRAY = new String[]{};
private static String[] EMPTY_STRING_ARRAY = {};
/**
* Backwards compatibility for wool colours in block syntax.
@ -169,11 +169,9 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
Property<Object> propertyKey = (Property<Object>) type.getPropertyMap().get(parts[0]);
if (propertyKey == null) {
if (context.getActor() != null) {
context.getActor().print(ErrorFormat.wrap("Unknown property ", parts[0], " for block ", type.getName(),
". Defaulting to base."));
throw new NoMatchException("Unknown property " + parts[0] + " for block " + type.getName());
} else {
WorldEdit.logger.warn("Unknown property " + parts[0] + " for block " + type.getName());
// throw new NoMatchException("Unknown property " + parts[0] + " for block " + type.getName());
}
return Maps.newHashMap();
}
@ -202,8 +200,29 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
@Override
public Stream<String> getSuggestions(String input) {
// TODO Include states
return BlockType.REGISTRY.keySet().stream();
final int idx = input.lastIndexOf('[');
if (idx < 0) {
if (input.indexOf(':') == -1) {
String key = ("minecraft:" + input).toLowerCase(Locale.ROOT);
return BlockType.REGISTRY.keySet().stream().filter(s -> s.startsWith(key));
}
if (input.contains(",")) {
return Stream.empty();
}
return BlockType.REGISTRY.keySet().stream();
}
String blockType = input.substring(0, idx);
BlockType type = BlockTypes.get(blockType.toLowerCase(Locale.ROOT));
if (type == null) {
return Stream.empty();
}
String props = input.substring(idx + 1);
if (props.isEmpty()) {
return type.getProperties().stream().map(p -> input + p.getName() + "=");
}
return SuggestionHelper.getBlockPropertySuggestions(blockType, props);
}
private BaseBlock parseLogic(String input, ParserContext context) throws InputParseException {
@ -238,6 +257,13 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
typeString = blockAndExtraData[0];
} else {
typeString = blockAndExtraData[0].substring(0, stateStart);
if (stateStart + 1 >= blockAndExtraData[0].length()) {
throw new InputParseException("Invalid format. Hanging bracket @ " + stateStart + ".");
}
int stateEnd = blockAndExtraData[0].lastIndexOf(']');
if (stateEnd < 0) {
throw new InputParseException("Invalid format. Unclosed property.");
}
stateString = blockAndExtraData[0].substring(stateStart + 1, blockAndExtraData[0].length() - 1);
}
if (typeString.isEmpty()) {

View File

@ -28,10 +28,7 @@ import com.sk89q.worldedit.world.item.ItemType;
import com.sk89q.worldedit.world.item.ItemTypes;
import com.sk89q.worldedit.world.registry.LegacyMapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DefaultItemParser extends InputParser<BaseItem> {
@ -42,7 +39,11 @@ public class DefaultItemParser extends InputParser<BaseItem> {
@Override
public Stream<String> getSuggestions(String input) {
return ItemType.REGISTRY.keySet().stream();
if (input.indexOf(':') == -1) {
input = "minecraft:" + input;
}
String key = input;
return ItemType.REGISTRY.keySet().stream().filter(s -> s.startsWith(key));
}
@Override
@ -58,8 +59,10 @@ public class DefaultItemParser extends InputParser<BaseItem> {
} else {
type = LegacyMapper.getInstance().getItemFromLegacy(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
}
item = new BaseItem(type);
} catch (NumberFormatException e) {
if (type != null) {
item = new BaseItem(type);
}
} catch (NumberFormatException ignored) {
}
}

View File

@ -35,6 +35,7 @@ import com.sk89q.worldedit.world.registry.BiomeRegistry;
import java.util.Collection;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Stream;
@ -46,7 +47,22 @@ public class BiomeMaskParser extends InputParser<Mask> {
@Override
public Stream<String> getSuggestions(String input) {
return BiomeType.REGISTRY.keySet().stream().map(biomeType -> "$" + biomeType);
final Stream<String> allBiomes = BiomeType.REGISTRY.keySet().stream().map(biomeType -> "$" + biomeType);
if (input.isEmpty()) {
return allBiomes;
}
if (input.charAt(0) == '$') {
String key = input.substring(1);
if (key.isEmpty()) {
return allBiomes;
}
if (key.indexOf(':') < 0) {
key = "minecraft:" + key;
}
String biomeId = key.toLowerCase(Locale.ROOT);
return BiomeType.REGISTRY.keySet().stream().filter(s -> s.startsWith(biomeId)).map(s -> "$" + s);
}
return Stream.empty();
}
@Override

View File

@ -20,6 +20,7 @@
package com.sk89q.worldedit.extension.factory.parser.mask;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.util.SuggestionHelper;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.BlockCategoryMask;
@ -39,7 +40,7 @@ public class BlockCategoryMaskParser extends InputParser<Mask> {
@Override
public Stream<String> getSuggestions(String input) {
return BlockCategory.REGISTRY.keySet().stream().map(str -> "##" + str);
return SuggestionHelper.getBlockCategorySuggestions(input, false);
}
@Override

View File

@ -28,12 +28,22 @@ import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.session.request.RequestExtent;
import java.util.stream.Stream;
public class BlockStateMaskParser extends InputParser<Mask> {
public BlockStateMaskParser(WorldEdit worldEdit) {
super(worldEdit);
}
@Override
public Stream<String> getSuggestions(String input) {
if (input.isEmpty()) {
return Stream.of("^[", "^=[");
}
return Stream.of("^[", "^=[").filter(s -> s.startsWith(input)); // no block type, can't suggest states
}
@Override
public Mask parseFromInput(String input, ParserContext context) throws InputParseException {
if (!(input.startsWith("^[") || input.startsWith("^=[")) || !input.endsWith("]")) {

View File

@ -28,7 +28,6 @@ import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.session.request.RequestExtent;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockCategory;
import java.util.Set;
import java.util.stream.Stream;

View File

@ -19,7 +19,7 @@
package com.sk89q.worldedit.extension.factory.parser.mask;
import com.google.common.collect.Lists;
import com.google.common.collect.ImmutableList;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.ExistingBlockMask;
@ -31,13 +31,15 @@ import java.util.List;
public class ExistingMaskParser extends SimpleInputParser<Mask> {
private final List<String> aliases = ImmutableList.of("#existing");
public ExistingMaskParser(WorldEdit worldEdit) {
super(worldEdit);
}
@Override
public List<String> getMatchedAliases() {
return Lists.newArrayList("#existing");
return aliases;
}
@Override

View File

@ -30,10 +30,10 @@ import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
import com.sk89q.worldedit.session.SessionOwner;
import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.session.request.RequestExtent;
import java.util.function.IntSupplier;
import java.util.stream.Stream;
public class ExpressionMaskParser extends InputParser<Mask> {
@ -41,6 +41,14 @@ public class ExpressionMaskParser extends InputParser<Mask> {
super(worldEdit);
}
@Override
public Stream<String> getSuggestions(String input) {
if (input.isEmpty()) {
return Stream.of("=");
}
return Stream.empty();
}
@Override
public Mask parseFromInput(String input, ParserContext context) throws InputParseException {
if (!input.startsWith("=")) {

View File

@ -19,9 +19,8 @@
package com.sk89q.worldedit.extension.factory.parser.mask;
import com.google.common.collect.Lists;
import com.google.common.collect.ImmutableList;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.RegionMask;
@ -32,17 +31,19 @@ import java.util.List;
public class LazyRegionMaskParser extends SimpleInputParser<Mask> {
private final List<String> aliases = ImmutableList.of("#dregion", "#dselection", "#dsel");
public LazyRegionMaskParser(WorldEdit worldEdit) {
super(worldEdit);
}
@Override
public List<String> getMatchedAliases() {
return Lists.newArrayList("#dregion", "#dselection", "#dsel");
return aliases;
}
@Override
public Mask parseFromSimpleInput(String input, ParserContext context) throws InputParseException {
public Mask parseFromSimpleInput(String input, ParserContext context) {
return new RegionMask(new RequestSelection());
}
}

View File

@ -26,12 +26,25 @@ import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.Masks;
import com.sk89q.worldedit.internal.registry.InputParser;
import java.util.stream.Stream;
public class NegateMaskParser extends InputParser<Mask> {
public NegateMaskParser(WorldEdit worldEdit) {
super(worldEdit);
}
@Override
public Stream<String> getSuggestions(String input) {
if (input.isEmpty()) {
return Stream.of("!");
}
if (input.charAt(0) != '!') {
return Stream.empty();
}
return worldEdit.getMaskFactory().getSuggestions(input.substring(1)).stream().map(s -> "!" + s);
}
@Override
public Mask parseFromInput(String input, ParserContext context) throws InputParseException {
if (!input.startsWith("!")) {

View File

@ -20,13 +20,14 @@
package com.sk89q.worldedit.extension.factory.parser.mask;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.NoiseFilter;
import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.math.noise.RandomNoise;
import java.util.stream.Stream;
public class NoiseMaskParser extends InputParser<Mask> {
public NoiseMaskParser(WorldEdit worldEdit) {
@ -34,7 +35,18 @@ public class NoiseMaskParser extends InputParser<Mask> {
}
@Override
public Mask parseFromInput(String input, ParserContext context) throws InputParseException {
public Stream<String> getSuggestions(String input) {
if (input.isEmpty()) {
return Stream.of("%");
}
if (input.charAt(0) != '%') {
return Stream.empty();
}
return Stream.of("%10", "%25", "%50", "%75").filter(s -> s.startsWith(input));
}
@Override
public Mask parseFromInput(String input, ParserContext context) {
if (!input.startsWith("%")) {
return null;
}

View File

@ -31,12 +31,26 @@ import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.session.request.RequestExtent;
import java.util.stream.Stream;
public class OffsetMaskParser extends InputParser<Mask> {
public OffsetMaskParser(WorldEdit worldEdit) {
super(worldEdit);
}
@Override
public Stream<String> getSuggestions(String input) {
if (input.isEmpty()) {
return Stream.of(">", "<");
}
final char firstChar = input.charAt(0);
if (firstChar != '>' && firstChar != '<') {
return Stream.empty();
}
return worldEdit.getMaskFactory().getSuggestions(input.substring(1)).stream().map(s -> firstChar + s);
}
@Override
public Mask parseFromInput(String input, ParserContext context) throws InputParseException {
final char firstChar = input.charAt(0);

View File

@ -19,7 +19,7 @@
package com.sk89q.worldedit.extension.factory.parser.mask;
import com.google.common.collect.Lists;
import com.google.common.collect.ImmutableList;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.input.InputParseException;
@ -32,13 +32,15 @@ import java.util.List;
public class RegionMaskParser extends SimpleInputParser<Mask> {
private final List<String> aliases = ImmutableList.of("#region", "#selection", "#sel");
public RegionMaskParser(WorldEdit worldEdit) {
super(worldEdit);
}
@Override
public List<String> getMatchedAliases() {
return Lists.newArrayList("#region", "#selection", "#sel");
return aliases;
}
@Override

View File

@ -19,7 +19,7 @@
package com.sk89q.worldedit.extension.factory.parser.mask;
import com.google.common.collect.Lists;
import com.google.common.collect.ImmutableList;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask;
@ -31,13 +31,15 @@ import java.util.List;
public class SolidMaskParser extends SimpleInputParser<Mask> {
private final List<String> aliases = ImmutableList.of("#solid");
public SolidMaskParser(WorldEdit worldEdit) {
super(worldEdit);
}
@Override
public List<String> getMatchedAliases() {
return Lists.newArrayList("#solid");
return aliases;
}
@Override

View File

@ -20,6 +20,7 @@
package com.sk89q.worldedit.extension.factory.parser.pattern;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.util.SuggestionHelper;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.pattern.BlockPattern;
@ -41,7 +42,7 @@ public class BlockCategoryPatternParser extends InputParser<Pattern> {
@Override
public Stream<String> getSuggestions(String input) {
return BlockCategory.REGISTRY.keySet().stream().map(str -> "##" + str);
return SuggestionHelper.getBlockCategorySuggestions(input, true);
}
@Override

View File

@ -31,6 +31,7 @@ import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.session.ClipboardHolder;
import java.util.Locale;
import java.util.stream.Stream;
public class ClipboardPatternParser extends InputParser<Pattern> {
@ -41,7 +42,27 @@ public class ClipboardPatternParser extends InputParser<Pattern> {
@Override
public Stream<String> getSuggestions(String input) {
return Stream.of("#clipboard", "#copy");
if (input.isEmpty()) {
return Stream.of("#clipoard");
}
String[] offsetParts = input.split("@", 2);
String firstLower = offsetParts[0].toLowerCase(Locale.ROOT);
final boolean isClip = "#clipboard".startsWith(firstLower);
final boolean isCopy = "#copy".startsWith(firstLower);
if (isClip || isCopy) {
if (offsetParts.length == 2) {
String coords = offsetParts[1];
if (coords.isEmpty()) {
return Stream.of(input + "[x,y,z]");
}
} else {
if (isClip) {
return Stream.of("#clipboard", "#clipboard@[x,y,z]");
}
return Stream.of("#copy", "#copy@[x,y,z]");
}
}
return Stream.empty();
}
@Override

View File

@ -28,6 +28,7 @@ import com.sk89q.worldedit.function.pattern.RandomPattern;
import com.sk89q.worldedit.internal.registry.InputParser;
import java.util.List;
import java.util.stream.Stream;
public class RandomPatternParser extends InputParser<Pattern> {
@ -35,12 +36,35 @@ public class RandomPatternParser extends InputParser<Pattern> {
super(worldEdit);
}
@Override
public Stream<String> getSuggestions(String input) {
String[] splits = input.split(",", -1);
List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true);
if (patterns.size() == 1) {
return Stream.empty();
}
// get suggestions for the last token only
String token = patterns.get(patterns.size() - 1);
String previous = String.join(",", patterns.subList(0, patterns.size() - 1));
if (token.matches("[0-9]+(\\.[0-9]*)?%.*")) {
String[] p = token.split("%");
if (p.length < 2) {
return Stream.empty();
} else {
token = p[1];
}
}
final List<String> innerSuggestions = worldEdit.getPatternFactory().getSuggestions(token);
return innerSuggestions.stream().map(s -> previous + "," + s);
}
@Override
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
RandomPattern randomPattern = new RandomPattern();
String[] splits = input.split(",");
List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']');
String[] splits = input.split(",", -1);
List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true);
if (patterns.size() == 1) {
return null; // let a 'single'-pattern parser handle it
}

View File

@ -29,11 +29,25 @@ import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.FuzzyBlockState;
import java.util.stream.Stream;
public class RandomStatePatternParser extends InputParser<Pattern> {
public RandomStatePatternParser(WorldEdit worldEdit) {
super(worldEdit);
}
@Override
public Stream<String> getSuggestions(String input) {
if (input.isEmpty()) {
return Stream.of("*");
}
if (!input.startsWith("*")) {
return Stream.empty();
}
return worldEdit.getBlockFactory().getSuggestions(input.substring(1)).stream().map(s -> "*" + s);
}
@Override
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
if (!input.startsWith("*")) {

View File

@ -25,7 +25,6 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.pattern.BlockPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.world.block.BlockType;
import java.util.stream.Stream;

View File

@ -20,9 +20,8 @@
package com.sk89q.worldedit.extension.factory.parser.pattern;
import com.google.common.base.Splitter;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.util.SuggestionHelper;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extent.Extent;
@ -32,10 +31,9 @@ import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.pattern.StateApplyingPattern;
import com.sk89q.worldedit.function.pattern.TypeApplyingPattern;
import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.world.block.BlockState;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
public class TypeOrStateApplyingPatternParser extends InputParser<Pattern> {
@ -44,6 +42,30 @@ public class TypeOrStateApplyingPatternParser extends InputParser<Pattern> {
super(worldEdit);
}
@Override
public Stream<String> getSuggestions(String input) {
if (input.isEmpty()) {
return Stream.of("^");
}
if (!input.startsWith("^")) {
return Stream.empty();
}
input = input.substring(1);
String[] parts = input.split("\\[", 2);
String type = parts[0];
if (parts.length == 1) {
return worldEdit.getBlockFactory().getSuggestions(input).stream().map(s -> "^" + s);
} else {
if (type.isEmpty()) {
return Stream.empty(); // without knowing a type, we can't really suggest states
} else {
return SuggestionHelper.getBlockPropertySuggestions(type, parts[1]).map(s -> "^" + s);
}
}
}
@Override
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
if (!input.startsWith("^")) {