From 5e857b3547f65378af12035b2f561ea99a3e570c Mon Sep 17 00:00:00 2001 From: wizjany Date: Mon, 27 May 2019 11:12:46 -0400 Subject: [PATCH] Improve parsing from registries. If multiple namespaces are present, suggestions will first suggest a namespace, then once a namespace is selected, keys within that namespace. Starting an argument with ":" will instead search across all namespaces for matching keys. --- .../command/argument/RegistryConverter.java | 12 +-- .../command/util/SuggestionHelper.java | 78 +++++++++++++------ .../factory/parser/DefaultBlockParser.java | 51 ++++++------ .../factory/parser/mask/BiomeMaskParser.java | 32 ++++---- .../registry/NamespacedRegistry.java | 29 ++++++- 5 files changed, 123 insertions(+), 79 deletions(-) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java index 264b1af0c..83999afa1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java @@ -20,8 +20,8 @@ package com.sk89q.worldedit.command.argument; import com.google.common.collect.ImmutableList; +import com.sk89q.worldedit.command.util.SuggestionHelper; import com.sk89q.worldedit.registry.Keyed; -import com.sk89q.worldedit.registry.NamespacedRegistry; import com.sk89q.worldedit.registry.Registry; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; @@ -45,8 +45,7 @@ import org.enginehub.piston.inject.Key; import java.lang.reflect.Field; import java.util.List; - -import static org.enginehub.piston.converter.SuggestionHelper.limitByPrefix; +import java.util.stream.Collectors; public final class RegistryConverter implements ArgumentConverter { @@ -85,12 +84,10 @@ public final class RegistryConverter implements ArgumentConvert private final Registry registry; private final TextComponent choices; - private final boolean namespaced; private RegistryConverter(Registry registry) { this.registry = registry; this.choices = TextComponent.of("any " + registry.getName()); - this.namespaced = registry instanceof NamespacedRegistry; } @Override @@ -109,9 +106,6 @@ public final class RegistryConverter implements ArgumentConvert @Override public List getSuggestions(String input) { - if (namespaced && input.indexOf(':') < 0) { - input = "minecraft:" + input; - } - return limitByPrefix(registry.keySet().stream(), input); + return SuggestionHelper.getRegistrySuggestions(registry, input).collect(Collectors.toList()); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SuggestionHelper.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SuggestionHelper.java index acf18f041..e0014ee56 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SuggestionHelper.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SuggestionHelper.java @@ -19,6 +19,9 @@ package com.sk89q.worldedit.command.util; +import com.sk89q.worldedit.registry.Keyed; +import com.sk89q.worldedit.registry.NamespacedRegistry; +import com.sk89q.worldedit.registry.Registry; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.world.block.BlockCategory; import com.sk89q.worldedit.world.block.BlockType; @@ -30,39 +33,32 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.enginehub.piston.converter.SuggestionHelper.byPrefix; +import static org.enginehub.piston.converter.SuggestionHelper.limitByPrefix; + +/** + * Internal class for generating common command suggestions. + */ public final class SuggestionHelper { private SuggestionHelper() { } public static Stream getBlockCategorySuggestions(String tag, boolean allowRandom) { - final Stream allTags = BlockCategory.REGISTRY.keySet().stream().map(str -> "##" + str); - if (tag.isEmpty()) { - return allTags; + if (tag.isEmpty() || tag.equals("#")) { + return Stream.of("##", "##*"); } if (tag.startsWith("#")) { - String key; - if (tag.startsWith("##")) { - key = tag.substring(2); - if (key.isEmpty()) { - return allTags; - } - boolean anyState = false; - if (allowRandom && key.charAt(0) == '*') { - key = key.substring(1); - anyState = true; - } - if (key.indexOf(':') < 0) { - key = "minecraft:" + key; - } - String finalTag = key.toLowerCase(Locale.ROOT); - final Stream stream = BlockCategory.REGISTRY.keySet().stream().filter(s -> - s.startsWith(finalTag)); - return anyState ? stream.map(s -> "##*" + s) : stream.map(s -> "##" + s); - } else if (tag.length() == 1) { - return allTags; + if (tag.equals("##")) { + return Stream.concat(Stream.of("##*"), getNamespacedRegistrySuggestions(BlockCategory.REGISTRY, tag.substring(2)).map(s -> "##" + s)); + } else if (tag.equals("##*") && allowRandom) { + return getNamespacedRegistrySuggestions(BlockCategory.REGISTRY, tag.substring(3)).map(s -> "##*" + s); + } else { + boolean wild = tag.startsWith("##*") && allowRandom; + return getNamespacedRegistrySuggestions(BlockCategory.REGISTRY, tag.substring(wild ? 3 : 2)).map(s -> (wild ? "##*" : "##") + s); } } return Stream.empty(); @@ -103,7 +99,7 @@ public final class SuggestionHelper { } else { Property prop = propertyMap.get(matchProp); if (prop == null) { - return Stream.empty(); + return propertyMap.keySet().stream().map(p -> lastValidInput + p); } final List values = prop.getValues().stream().map(v -> v.toString().toLowerCase(Locale.ROOT)).collect(Collectors.toList()); String matchVal = propVal[1].toLowerCase(Locale.ROOT); @@ -140,4 +136,38 @@ public final class SuggestionHelper { } return Stream.empty(); } + + public static Stream getRegistrySuggestions(Registry registry, String input) { + if (registry instanceof NamespacedRegistry) { + return getNamespacedRegistrySuggestions(((NamespacedRegistry) registry), input); + } + return limitByPrefix(registry.keySet().stream(), input).stream(); + } + + public static Stream getNamespacedRegistrySuggestions(NamespacedRegistry registry, String input) { + if (input.isEmpty() || input.equals(":")) { + final Set namespaces = registry.getKnownNamespaces(); + if (namespaces.size() == 1) { + return registry.keySet().stream(); + } else { + return namespaces.stream().map(s -> s + ":"); + } + } + if (input.startsWith(":")) { // special case - search across namespaces + final String term = input.substring(1).toLowerCase(Locale.ROOT); + Predicate search = byPrefix(term); + return registry.keySet().stream().filter(s -> search.test(s.substring(s.indexOf(':') + 1))); + } + // otherwise, we actually have some text to search + if (input.indexOf(':') < 0) { + // don't yet have namespace - search namespaces + default + final String lowerSearch = input.toLowerCase(Locale.ROOT); + String defKey = registry.getDefaultNamespace() + ":" + lowerSearch; + return Stream.concat(registry.keySet().stream().filter(s -> s.startsWith(defKey)), + registry.getKnownNamespaces().stream().filter(n -> n.startsWith(lowerSearch)).map(n -> n + ":")); + } + // have a namespace - search that + Predicate search = byPrefix(input.toLowerCase(Locale.ROOT)); + return registry.keySet().stream().filter(search); + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/DefaultBlockParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/DefaultBlockParser.java index 0bdcdc9ae..9f4402715 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/DefaultBlockParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/DefaultBlockParser.java @@ -54,8 +54,6 @@ import java.util.Locale; import java.util.Map; import java.util.stream.Stream; -import static org.enginehub.piston.converter.SuggestionHelper.limitByPrefix; - /** * Parses block input strings. */ @@ -111,6 +109,7 @@ public class DefaultBlockParser extends InputParser { * @param string Input string * @return Mapped string */ + @SuppressWarnings("ConstantConditions") private String woolMapper(String string) { switch (string.toLowerCase(Locale.ROOT)) { case "white": @@ -191,7 +190,7 @@ public class DefaultBlockParser extends InputParser { } catch (NoMatchException e) { throw e; // Pass-through } catch (Exception e) { - e.printStackTrace(); + WorldEdit.logger.warn("Unknown state '" + parseableData + "'", e); throw new NoMatchException("Unknown state '" + parseableData + "'"); } } @@ -204,14 +203,7 @@ public class DefaultBlockParser extends InputParser { public Stream getSuggestions(String input) { 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 limitByPrefix(BlockType.REGISTRY.keySet().stream(), input).stream(); + return SuggestionHelper.getNamespacedRegistrySuggestions(BlockType.REGISTRY, input); } String blockType = input.substring(0, idx); BlockType type = BlockTypes.get(blockType.toLowerCase(Locale.ROOT)); @@ -238,8 +230,10 @@ public class DefaultBlockParser extends InputParser { // Legacy matcher if (context.isTryingLegacy()) { try { - String[] split = blockAndExtraData[0].split(":"); - if (split.length == 1) { + String[] split = blockAndExtraData[0].split(":", 2); + if (split.length == 0) { + throw new InputParseException("Invalid colon."); + } else if (split.length == 1) { state = LegacyMapper.getInstance().getBlockFromLegacy(Integer.parseInt(split[0])); } else { state = LegacyMapper.getInstance().getBlockFromLegacy(Integer.parseInt(split[0]), Integer.parseInt(split[1])); @@ -247,7 +241,7 @@ public class DefaultBlockParser extends InputParser { if (state != null) { blockType = state.getBlockType(); } - } catch (NumberFormatException e) { + } catch (NumberFormatException ignored) { } } @@ -310,23 +304,15 @@ public class DefaultBlockParser extends InputParser { } else { // Attempt to lookup a block from ID or name. blockType = BlockTypes.get(typeString.toLowerCase(Locale.ROOT)); + } - if (blockType == null) { - throw new NoMatchException("Does not match a valid block type: '" + input + "'"); - } + if (blockType == null) { + throw new NoMatchException("Does not match a valid block type: '" + input + "'"); } blockStates.putAll(parseProperties(blockType, stateProperties, context)); - if (!context.isPreferringWildcard()) { - // No wildcards allowed => eliminate them. (Start with default state) - state = blockType.getDefaultState(); - for (Map.Entry, Object> blockState : blockStates.entrySet()) { - @SuppressWarnings("unchecked") - Property objProp = (Property) blockState.getKey(); - state = state.with(objProp, blockState.getValue()); - } - } else { + if (context.isPreferringWildcard()) { FuzzyBlockState.Builder fuzzyBuilder = FuzzyBlockState.builder(); fuzzyBuilder.type(blockType); for (Map.Entry, Object> blockState : blockStates.entrySet()) { @@ -335,8 +321,20 @@ public class DefaultBlockParser extends InputParser { fuzzyBuilder.withProperty(objProp, blockState.getValue()); } state = fuzzyBuilder.build(); + } else { + // No wildcards allowed => eliminate them. (Start with default state) + state = blockType.getDefaultState(); + for (Map.Entry, Object> blockState : blockStates.entrySet()) { + @SuppressWarnings("unchecked") + Property objProp = (Property) blockState.getKey(); + state = state.with(objProp, blockState.getValue()); + } } } + // this should be impossible but IntelliJ isn't that smart + if (blockType == null) { + throw new NoMatchException("Does not match a valid block type: '" + input + "'"); + } // Check if the item is allowed if (context.isRestricted()) { @@ -369,6 +367,7 @@ public class DefaultBlockParser extends InputParser { } return new MobSpawnerBlock(state, mobName); } else { + //noinspection ConstantConditions return new MobSpawnerBlock(state, EntityTypes.PIG.getId()); } } else if (blockType == BlockTypes.PLAYER_HEAD || blockType == BlockTypes.PLAYER_WALL_HEAD) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BiomeMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BiomeMaskParser.java index db0ab21e7..9e38e2e82 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BiomeMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BiomeMaskParser.java @@ -21,22 +21,20 @@ package com.sk89q.worldedit.extension.factory.parser.mask; import com.google.common.base.Splitter; 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.extension.platform.Capability; import com.sk89q.worldedit.function.mask.BiomeMask2D; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Masks; import com.sk89q.worldedit.internal.registry.InputParser; import com.sk89q.worldedit.session.request.RequestExtent; import com.sk89q.worldedit.world.biome.BiomeType; -import com.sk89q.worldedit.world.biome.Biomes; -import com.sk89q.worldedit.world.registry.BiomeRegistry; -import java.util.Collection; +import java.util.Arrays; import java.util.HashSet; -import java.util.Locale; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; public class BiomeMaskParser extends InputParser { @@ -47,20 +45,20 @@ public class BiomeMaskParser extends InputParser { @Override public Stream getSuggestions(String input) { - final Stream allBiomes = BiomeType.REGISTRY.keySet().stream().map(biomeType -> "$" + biomeType); if (input.isEmpty()) { - return allBiomes; + return Stream.of("$"); } if (input.charAt(0) == '$') { - String key = input.substring(1); - if (key.isEmpty()) { - return allBiomes; + input = input.substring(1); + final int lastTermIdx = input.lastIndexOf(','); + if (lastTermIdx <= 0) { + return SuggestionHelper.getNamespacedRegistrySuggestions(BiomeType.REGISTRY, input).map(s -> "$" + s); } - 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); + String prev = input.substring(0, lastTermIdx) + ","; + Set prevBiomes = Arrays.stream(prev.split(",", 0)).collect(Collectors.toSet()); + String search = input.substring(lastTermIdx + 1); + return SuggestionHelper.getNamespacedRegistrySuggestions(BiomeType.REGISTRY, search) + .filter(s -> !prevBiomes.contains(s)).map(s -> "$" + prev + s); } return Stream.empty(); } @@ -72,10 +70,8 @@ public class BiomeMaskParser extends InputParser { } Set biomes = new HashSet<>(); - BiomeRegistry biomeRegistry = worldEdit.getPlatformManager().queryCapability(Capability.GAME_HOOKS).getRegistries().getBiomeRegistry(); - Collection knownBiomes = BiomeType.REGISTRY.values(); for (String biomeName : Splitter.on(",").split(input.substring(1))) { - BiomeType biome = Biomes.findBiomeByName(knownBiomes, biomeName, biomeRegistry); + BiomeType biome = BiomeType.REGISTRY.get(biomeName); if (biome == null) { throw new InputParseException("Unknown biome '" + biomeName + '\''); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java index 4519bbc88..893cbd018 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java @@ -23,9 +23,13 @@ import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; import javax.annotation.Nullable; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; public final class NamespacedRegistry extends Registry { private static final String MINECRAFT_NAMESPACE = "minecraft"; + private final Set knownNamespaces = new HashSet<>(); private final String defaultNamespace; public NamespacedRegistry(final String name) { @@ -46,8 +50,29 @@ public final class NamespacedRegistry extends Registry { @Override public V register(final String key, final V value) { requireNonNull(key, "key"); - checkState(key.indexOf(':') > -1, "key is not namespaced"); - return super.register(key, value); + final int i = key.indexOf(':'); + checkState(i > 0, "key is not namespaced"); + final V registered = super.register(key, value); + knownNamespaces.add(key.substring(0, i)); + return registered; + } + + /** + * Get a set of the namespaces of all registered keys. + * + * @return set of namespaces + */ + public Set getKnownNamespaces() { + return Collections.unmodifiableSet(knownNamespaces); + } + + /** + * Get the default namespace for this registry. + * + * @return the default namespace + */ + public String getDefaultNamespace() { + return defaultNamespace; } private String orDefaultNamespace(final String key) {