From 6962b2e7b6ae019e2c861ad9f491328c82846a13 Mon Sep 17 00:00:00 2001 From: wizjany Date: Sun, 26 May 2019 02:20:02 -0400 Subject: [PATCH] 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. --- config/checkstyle/import-control.xml | 2 +- worldedit-bukkit/build.gradle | 5 +- .../sk89q/worldedit/bukkit/BukkitAdapter.java | 2 +- .../worldedit/bukkit/WorldEditPlugin.java | 39 ++++- .../main/java/com/sk89q/util/StringUtil.java | 9 +- .../worldedit/command/RegionCommands.java | 2 +- .../argument/EntityRemoverConverter.java | 13 ++ .../command/argument/FactoryConverter.java | 2 - .../command/argument/RegistryConverter.java | 6 + .../command/factory/ItemUseFactory.java | 2 +- .../command/util/SuggestionHelper.java | 143 ++++++++++++++++++ .../extension/factory/BlockFactory.java | 2 +- .../extension/factory/MaskFactory.java | 6 +- .../extension/factory/PatternFactory.java | 2 +- .../factory/parser/DefaultBlockParser.java | 40 ++++- .../factory/parser/DefaultItemParser.java | 15 +- .../factory/parser/mask/BiomeMaskParser.java | 18 ++- .../parser/mask/BlockCategoryMaskParser.java | 3 +- .../parser/mask/BlockStateMaskParser.java | 10 ++ .../factory/parser/mask/BlocksMaskParser.java | 1 - .../parser/mask/ExistingMaskParser.java | 6 +- .../parser/mask/ExpressionMaskParser.java | 10 +- .../parser/mask/LazyRegionMaskParser.java | 9 +- .../factory/parser/mask/NegateMaskParser.java | 13 ++ .../factory/parser/mask/NoiseMaskParser.java | 16 +- .../factory/parser/mask/OffsetMaskParser.java | 14 ++ .../factory/parser/mask/RegionMaskParser.java | 6 +- .../factory/parser/mask/SolidMaskParser.java | 6 +- .../pattern/BlockCategoryPatternParser.java | 3 +- .../pattern/ClipboardPatternParser.java | 23 ++- .../parser/pattern/RandomPatternParser.java | 28 +++- .../pattern/RandomStatePatternParser.java | 14 ++ .../pattern/SingleBlockPatternParser.java | 1 - .../TypeOrStateApplyingPatternParser.java | 30 +++- .../internal/registry/AbstractFactory.java | 7 +- .../internal/registry/InputParser.java | 4 +- .../internal/registry/SimpleInputParser.java | 15 +- 37 files changed, 463 insertions(+), 64 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SuggestionHelper.java diff --git a/config/checkstyle/import-control.xml b/config/checkstyle/import-control.xml index c783552a9..a291b5ad5 100644 --- a/config/checkstyle/import-control.xml +++ b/config/checkstyle/import-control.xml @@ -44,7 +44,7 @@ - + diff --git a/worldedit-bukkit/build.gradle b/worldedit-bukkit/build.gradle index 21b5bdebb..8076e2ca1 100644 --- a/worldedit-bukkit/build.gradle +++ b/worldedit-bukkit/build.gradle @@ -19,7 +19,8 @@ dependencies { compile project(':worldedit-libs:bukkit') compile 'com.sk89q:dummypermscompat:1.10' compile 'org.bukkit:bukkit:1.13.2-R0.1-SNAPSHOT' // zzz - compile "io.papermc:paperlib:1.0.1" + compile 'com.destroystokyo.paper:paper-api:1.13.2-R0.1-SNAPSHOT' + compile "io.papermc:paperlib:1.0.2" compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.1' compile 'org.bstats:bstats-bukkit:1.4' testCompile 'org.mockito:mockito-core:1.9.0-rc1' @@ -53,7 +54,7 @@ shadowJar { include(dependency("org.bstats:bstats-bukkit:1.4")) } relocate ("io.papermc.lib", "com.sk89q.worldedit.bukkit.paperlib") { - include(dependency("io.papermc:paperlib:1.0.1")) + include(dependency("io.papermc:paperlib:1.0.2")) } } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitAdapter.java index d02bd1abc..b8418d03d 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitAdapter.java @@ -151,7 +151,7 @@ public class BukkitAdapter { if (match != null) { return match; } else { - throw new IllegalArgumentException("Can't find a Bukkit world for " + world); + throw new IllegalArgumentException("Can't find a Bukkit world for " + world.getName()); } } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index 1fb7633bf..570ef30be 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -49,6 +49,7 @@ import com.sk89q.worldedit.world.gamemode.GameModes; import com.sk89q.worldedit.world.item.ItemCategory; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.weather.WeatherTypes; +import io.papermc.lib.PaperLib; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -75,6 +76,7 @@ import java.io.InputStream; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.zip.ZipEntry; @@ -122,6 +124,10 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { // Now we can register events getServer().getPluginManager().registerEvents(new WorldEditListener(this), this); + // register async tab complete, if available + if (PaperLib.isPaper()) { + getServer().getPluginManager().registerEvents(new AsyncTabCompleteListener(), this); + } // register this so we can load world-dependent data right as the first world is loading if (worldInitListener != null) { @@ -138,6 +144,7 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { // Enable metrics new Metrics(this); + PaperLib.suggestPaper(this); } private void setupWorldData() { @@ -171,11 +178,12 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { ).toImmutableState(); BlockState defaultState = blockState.getBlockType().getAllStates().get(0); for (Map.Entry, Object> propertyObjectEntry : state.getStates().entrySet()) { - defaultState = defaultState.with((Property) propertyObjectEntry.getKey(), propertyObjectEntry.getValue()); + //noinspection unchecked + defaultState = defaultState.with((Property) propertyObjectEntry.getKey(), propertyObjectEntry.getValue()); } return defaultState; } catch (InputParseException e) { - e.printStackTrace(); + getLogger().log(Level.WARNING, "Error loading block state for " + material.getKey(), e); return blockState; } })); @@ -206,7 +214,7 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { for (Tag itemTag : Bukkit.getTags(Tag.REGISTRY_ITEMS, Material.class)) { ItemCategory.REGISTRY.register(itemTag.getKey().toString(), new ItemCategory(itemTag.getKey().toString())); } - } catch (NoSuchMethodError e) { + } catch (NoSuchMethodError ignored) { getLogger().warning("The version of Spigot/Paper you are using doesn't support Tags. The usage of tags with WorldEdit will not work until you update."); } } @@ -458,4 +466,29 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { setupWorldData(); } } + + private class AsyncTabCompleteListener implements Listener { + AsyncTabCompleteListener() { + } + + @SuppressWarnings("UnnecessaryFullyQualifiedName") + @EventHandler(ignoreCancelled = true) + public void onAsyncTabComplete(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event) { + if (!event.isCommand()) return; + + String buffer = event.getBuffer(); + final String[] parts = buffer.split(" "); + if (parts.length < 1) return; + final String label = parts[0]; + final Optional command + = WorldEdit.getInstance().getPlatformManager().getPlatformCommandManager().getCommandManager().getCommand(label); + if (!command.isPresent()) return; + + CommandSuggestionEvent suggestEvent = new CommandSuggestionEvent(wrapCommandSender(event.getSender()), event.getBuffer()); + getWorldEdit().getEventBus().post(suggestEvent); + + event.setCompletions(CommandUtil.fixSuggestions(event.getBuffer(), suggestEvent.getSuggestions())); + event.setHandled(true); + } + } } diff --git a/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java b/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java index 80792b846..dd4bf4b29 100644 --- a/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java +++ b/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java @@ -306,7 +306,11 @@ public final class StringUtil { } public static List parseListInQuotes(String[] input, char delimiter, char quoteOpen, char quoteClose) { - List parsableBlocks = new ArrayList<>(); + return parseListInQuotes(input, delimiter, quoteOpen, quoteClose, false); + } + + public static List parseListInQuotes(String[] input, char delimiter, char quoteOpen, char quoteClose, boolean appendLeftover) { + List parsableBlocks = new ArrayList<>(); StringBuilder buffer = new StringBuilder(); for (String split : input) { if (split.indexOf(quoteOpen) != -1 && split.indexOf(quoteClose) == -1) { @@ -321,6 +325,9 @@ public final class StringUtil { buffer.append(split).append(delimiter); } } + if (appendLeftover && buffer.length() != 0) { + parsableBlocks.add(buffer.delete(buffer.length() - 1, buffer.length()).toString()); + } return parsableBlocks; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index ec3c09e4a..262cc57f6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -89,7 +89,7 @@ public class RegionCommands { @Logging(REGION) public int set(Player player, EditSession editSession, @Selection Region region, - @Arg(desc = "The patter of blocks to set") + @Arg(desc = "The pattern of blocks to set") Pattern pattern) { RegionFunction set = new BlockReplace(editSession, pattern); RegionVisitor visitor = new RegionVisitor(region, set); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/EntityRemoverConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/EntityRemoverConverter.java index d2e4b6e35..cdbc63e53 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/EntityRemoverConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/EntityRemoverConverter.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.command.argument; +import com.google.common.collect.ImmutableList; import com.sk89q.worldedit.command.util.EntityRemover; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; @@ -30,12 +31,19 @@ import org.enginehub.piston.converter.SuccessfulConversion; import org.enginehub.piston.inject.InjectedValueAccess; import org.enginehub.piston.inject.Key; +import java.util.List; + +import static org.enginehub.piston.converter.SuggestionHelper.limitByPrefix; + public class EntityRemoverConverter implements ArgumentConverter { public static void register(CommandManager commandManager) { commandManager.registerConverter(Key.of(EntityRemover.class), new EntityRemoverConverter()); } + private final List suggestions + = ImmutableList.of("projectiles", "items", "paintings", "itemframes", "boats", "minecarts", "tnt", "xp", "all"); + private EntityRemoverConverter() { } @@ -46,6 +54,11 @@ public class EntityRemoverConverter implements ArgumentConverter ); } + @Override + public List getSuggestions(String input) { + return limitByPrefix(suggestions.stream(), input); + } + @Override public ConversionResult convert(String argument, InjectedValueAccess context) { try { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/FactoryConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/FactoryConverter.java index d28f15ec9..d6259abf3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/FactoryConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/FactoryConverter.java @@ -19,8 +19,6 @@ package com.sk89q.worldedit.command.argument; -import static org.enginehub.piston.converter.SuggestionHelper.limitByPrefix; - import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.blocks.BaseItem; 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 a37c5b174..264b1af0c 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 @@ -21,6 +21,7 @@ package com.sk89q.worldedit.command.argument; import com.google.common.collect.ImmutableList; 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; @@ -84,10 +85,12 @@ 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 @@ -106,6 +109,9 @@ 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); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/ItemUseFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/ItemUseFactory.java index 8b25780d4..a099a78a1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/ItemUseFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/ItemUseFactory.java @@ -42,6 +42,6 @@ public final class ItemUseFactory implements Contextual { @Override public String toString() { - return "application of the item " + item.getType() + ":" + item.getNbtData(); + return "application of the item " + item.getType(); } } 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 new file mode 100644 index 000000000..acf18f041 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SuggestionHelper.java @@ -0,0 +1,143 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.command.util; + +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.block.BlockCategory; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +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.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; + } + } + return Stream.empty(); + } + + public static Stream getBlockPropertySuggestions(String blockType, String props) { + BlockType type = BlockTypes.get(blockType.toLowerCase(Locale.ROOT)); + if (type == null) { + return Stream.empty(); + } + final Map> propertyMap = type.getPropertyMap(); + Set matchedProperties = new HashSet<>(); + String[] propParts = props.split(",", -1); + for (int i = 0; i < propParts.length; i++) { + String[] propVal = propParts[i].split("="); + final String matchProp = propVal[0].toLowerCase(Locale.ROOT); + if (i == propParts.length - 1) { + // suggest for next property + String previous = Arrays.stream(propParts, 0, propParts.length - 1).collect(Collectors.joining(",")) + + (propParts.length == 1 ? "" : ","); + String lastValidInput = (blockType + "[" + previous).toLowerCase(Locale.ROOT); + if (propVal.length == 1) { + // only property, no value yet + final List> matchingProps = propertyMap.entrySet().stream() + .filter(p -> !matchedProperties.contains(p.getKey()) && p.getKey().startsWith(matchProp)) + .map(Map.Entry::getValue).collect(Collectors.toList()); + switch (matchingProps.size()) { + case 0: + return propertyMap.keySet().stream().filter(p -> !matchedProperties.contains(p)).map(prop -> + lastValidInput + prop + "="); + case 1: + return matchingProps.get(0).getValues().stream().map(val -> + lastValidInput + matchingProps.get(0).getName() + "=" + + val.toString().toLowerCase(Locale.ROOT)); + default: + return matchingProps.stream().map(p -> lastValidInput + p.getName() + "="); + } + } else { + Property prop = propertyMap.get(matchProp); + if (prop == null) { + return Stream.empty(); + } + final List values = prop.getValues().stream().map(v -> v.toString().toLowerCase(Locale.ROOT)).collect(Collectors.toList()); + String matchVal = propVal[1].toLowerCase(Locale.ROOT); + List matchingVals = values.stream().filter(val -> val.startsWith(matchVal)).collect(Collectors.toList()); + if (matchingVals.isEmpty()) { + return values.stream().map(val -> lastValidInput + prop.getName() + "=" + val); + } else { + if (matchingVals.size() == 1 && matchingVals.get(0).equals(matchVal)) { + String currProp = lastValidInput + prop.getName() + "=" + matchVal; + if (matchingVals.size() < values.size()) { + return Stream.of(currProp + "] ", currProp + ","); + } + return Stream.of(currProp + "] "); + } + return matchingVals.stream().map(val -> lastValidInput + prop.getName() + "=" + val); + } + } + } else { + // validate previous properties + if (propVal.length != 2) { + return Stream.empty(); + } + Property prop = propertyMap.get(matchProp); + if (prop == null) { + return Stream.empty(); + } + try { + prop.getValueFor(propVal[1]); + matchedProperties.add(prop.getName()); + } catch (IllegalArgumentException ignored) { + return Stream.empty(); + } + } + } + return Stream.empty(); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/BlockFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/BlockFactory.java index 235f0ae3e..c00583183 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/BlockFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/BlockFactory.java @@ -61,7 +61,7 @@ public class BlockFactory extends AbstractFactory { public Set parseFromListInput(String input, ParserContext context) throws InputParseException { Set 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; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/MaskFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/MaskFactory.java index 29bb9eb25..31b1ac2df 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/MaskFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/MaskFactory.java @@ -64,13 +64,15 @@ public final class MaskFactory extends AbstractFactory { 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)); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java index 8457f3fc3..0df1dc40c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java @@ -50,10 +50,10 @@ public final class PatternFactory extends AbstractFactory { 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)); 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 a2d069bd8..4a03a5471 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 @@ -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 { } } - 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 { Property propertyKey = (Property) 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 { @Override public Stream 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 { 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()) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/DefaultItemParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/DefaultItemParser.java index 702e54458..e325b24a1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/DefaultItemParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/DefaultItemParser.java @@ -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 { @@ -42,7 +39,11 @@ public class DefaultItemParser extends InputParser { @Override public Stream 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 { } 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) { } } 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 d484b4ff9..db0ab21e7 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 @@ -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 { @Override public Stream getSuggestions(String input) { - return BiomeType.REGISTRY.keySet().stream().map(biomeType -> "$" + biomeType); + final Stream 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 diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockCategoryMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockCategoryMaskParser.java index 630386b56..ee7bebf44 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockCategoryMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockCategoryMaskParser.java @@ -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 { @Override public Stream getSuggestions(String input) { - return BlockCategory.REGISTRY.keySet().stream().map(str -> "##" + str); + return SuggestionHelper.getBlockCategorySuggestions(input, false); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java index 0a2bd6e56..95884b821 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java @@ -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 { public BlockStateMaskParser(WorldEdit worldEdit) { super(worldEdit); } + @Override + public Stream 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("]")) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlocksMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlocksMaskParser.java index df7ed3a16..8580eb8a1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlocksMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlocksMaskParser.java @@ -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; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExistingMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExistingMaskParser.java index 5249d47ad..21feb6449 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExistingMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExistingMaskParser.java @@ -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 { + private final List aliases = ImmutableList.of("#existing"); + public ExistingMaskParser(WorldEdit worldEdit) { super(worldEdit); } @Override public List getMatchedAliases() { - return Lists.newArrayList("#existing"); + return aliases; } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java index 3087610be..ac104e198 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java @@ -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 { @@ -41,6 +41,14 @@ public class ExpressionMaskParser extends InputParser { super(worldEdit); } + @Override + public Stream 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("=")) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/LazyRegionMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/LazyRegionMaskParser.java index 82bc14d2b..29b7af7a9 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/LazyRegionMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/LazyRegionMaskParser.java @@ -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 { + private final List aliases = ImmutableList.of("#dregion", "#dselection", "#dsel"); + public LazyRegionMaskParser(WorldEdit worldEdit) { super(worldEdit); } @Override public List 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()); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/NegateMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/NegateMaskParser.java index 9e1c2e9df..e8e4ddf57 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/NegateMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/NegateMaskParser.java @@ -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 { public NegateMaskParser(WorldEdit worldEdit) { super(worldEdit); } + @Override + public Stream 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("!")) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/NoiseMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/NoiseMaskParser.java index 0cb1a85e8..40f54ebfe 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/NoiseMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/NoiseMaskParser.java @@ -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 { public NoiseMaskParser(WorldEdit worldEdit) { @@ -34,7 +35,18 @@ public class NoiseMaskParser extends InputParser { } @Override - public Mask parseFromInput(String input, ParserContext context) throws InputParseException { + public Stream 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; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java index ab4882e00..ede9b6c79 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java @@ -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 { public OffsetMaskParser(WorldEdit worldEdit) { super(worldEdit); } + @Override + public Stream 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); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/RegionMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/RegionMaskParser.java index 21963835d..bd7ceaa3e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/RegionMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/RegionMaskParser.java @@ -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 { + private final List aliases = ImmutableList.of("#region", "#selection", "#sel"); + public RegionMaskParser(WorldEdit worldEdit) { super(worldEdit); } @Override public List getMatchedAliases() { - return Lists.newArrayList("#region", "#selection", "#sel"); + return aliases; } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SolidMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SolidMaskParser.java index 44e2f07a2..f5ae3244a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SolidMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SolidMaskParser.java @@ -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 { + private final List aliases = ImmutableList.of("#solid"); + public SolidMaskParser(WorldEdit worldEdit) { super(worldEdit); } @Override public List getMatchedAliases() { - return Lists.newArrayList("#solid"); + return aliases; } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BlockCategoryPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BlockCategoryPatternParser.java index f9e0f8052..3260fa438 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BlockCategoryPatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BlockCategoryPatternParser.java @@ -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 { @Override public Stream getSuggestions(String input) { - return BlockCategory.REGISTRY.keySet().stream().map(str -> "##" + str); + return SuggestionHelper.getBlockCategorySuggestions(input, true); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/ClipboardPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/ClipboardPatternParser.java index 4029c4359..532a981e6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/ClipboardPatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/ClipboardPatternParser.java @@ -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 { @@ -41,7 +42,27 @@ public class ClipboardPatternParser extends InputParser { @Override public Stream 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 diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java index 68c768954..81b1b11e3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java @@ -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 { @@ -35,12 +36,35 @@ public class RandomPatternParser extends InputParser { super(worldEdit); } + @Override + public Stream getSuggestions(String input) { + String[] splits = input.split(",", -1); + List 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 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 patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']'); + String[] splits = input.split(",", -1); + List patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true); if (patterns.size() == 1) { return null; // let a 'single'-pattern parser handle it } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomStatePatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomStatePatternParser.java index 27423f59e..3b6dc5c1f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomStatePatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomStatePatternParser.java @@ -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 { public RandomStatePatternParser(WorldEdit worldEdit) { super(worldEdit); } + @Override + public Stream 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("*")) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SingleBlockPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SingleBlockPatternParser.java index 3bd852513..30f844611 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SingleBlockPatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SingleBlockPatternParser.java @@ -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; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java index db0ee8c38..8897b115a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java @@ -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 { @@ -44,6 +42,30 @@ public class TypeOrStateApplyingPatternParser extends InputParser { super(worldEdit); } + @Override + public Stream 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("^")) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/AbstractFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/AbstractFactory.java index d4ec8bef8..f1b3e05aa 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/AbstractFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/AbstractFactory.java @@ -20,7 +20,6 @@ package com.sk89q.worldedit.internal.registry; import static com.google.common.base.Preconditions.checkNotNull; -import static org.enginehub.piston.converter.SuggestionHelper.limitByPrefix; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.input.InputParseException; @@ -28,11 +27,9 @@ import com.sk89q.worldedit.extension.input.NoMatchException; import com.sk89q.worldedit.extension.input.ParserContext; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * An abstract implementation of a factory for internal usage. @@ -81,7 +78,9 @@ public abstract class AbstractFactory { } public List getSuggestions(String input) { - return limitByPrefix(parsers.stream().flatMap(parser -> parser.getSuggestions(input)), input); + return parsers.stream().flatMap( + p -> p.getSuggestions(input) + ).collect(Collectors.toList()); } /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/InputParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/InputParser.java index 5862d9cf9..febafacc8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/InputParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/InputParser.java @@ -23,8 +23,6 @@ import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; -import java.util.Collections; -import java.util.List; import java.util.stream.Stream; /** @@ -37,7 +35,7 @@ public abstract class InputParser { protected final WorldEdit worldEdit; - public InputParser(WorldEdit worldEdit) { + protected InputParser(WorldEdit worldEdit) { this.worldEdit = worldEdit; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/SimpleInputParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/SimpleInputParser.java index 013e45958..aace1ee89 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/SimpleInputParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/registry/SimpleInputParser.java @@ -19,12 +19,12 @@ package com.sk89q.worldedit.internal.registry; -import com.google.common.collect.Lists; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; import java.util.List; +import java.util.Locale; import java.util.stream.Stream; /** @@ -34,7 +34,7 @@ import java.util.stream.Stream; */ public abstract class SimpleInputParser extends InputParser { - public SimpleInputParser(WorldEdit worldEdit) { + protected SimpleInputParser(WorldEdit worldEdit) { super(worldEdit); } @@ -67,6 +67,15 @@ public abstract class SimpleInputParser extends InputParser { @Override public Stream getSuggestions(String input) { - return Stream.of(getPrimaryMatcher()); + if (input.isEmpty()) { + return Stream.of(getPrimaryMatcher()); + } + final String prefix = input.toLowerCase(Locale.ROOT); + for (String alias : getMatchedAliases()) { + if (alias.startsWith(prefix)) { + return Stream.of(alias); + } + } + return Stream.empty(); } }