From 20d93ad3a51afd07b3cbb0da198f866bd4f6604c Mon Sep 17 00:00:00 2001 From: James Lyne Date: Sat, 18 Dec 2021 23:56:14 +0000 Subject: [PATCH] /dmap tabcompletions --- .../src/main/java/org/dynmap/DynmapCore.java | 52 ++++++- .../java/org/dynmap/DynmapMapCommands.java | 147 ++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) diff --git a/DynmapCore/src/main/java/org/dynmap/DynmapCore.java b/DynmapCore/src/main/java/org/dynmap/DynmapCore.java index 9dfe7707..6858882d 100644 --- a/DynmapCore/src/main/java/org/dynmap/DynmapCore.java +++ b/DynmapCore/src/main/java/org/dynmap/DynmapCore.java @@ -31,6 +31,7 @@ import java.util.Scanner; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -1378,7 +1379,7 @@ public class DynmapCore implements DynmapCommonAPI { * @param arg - Partial world name to filter by * @return List of tab completion suggestions */ - List getWorldSuggestions(String arg) { + public List getWorldSuggestions(String arg) { return mapManager.getWorlds().stream() .map(DynmapWorld::getName) .filter(name -> name.startsWith(arg)) @@ -1432,6 +1433,51 @@ public class DynmapCore implements DynmapCommonAPI { return suggestions; } + /** + * Returns tab completion suggestions for field:value args based on the provided arguments + * If the last provided argument contains a ":", values for the field will be suggested if present + * Otherwise fields will be suggested if they do not already exist with a value in the provided arguments + * + * @param args - Array of already provided command arguments + * @param fields - Map of possible field names and suppliers for values + * @return List of tab completion suggestions + */ + public List getFieldValueSuggestions(String[] args, Map> fields) { + if (args.length == 0 || fields == null || fields.isEmpty()) { + return Collections.emptyList(); + } + + List suggestions = new ArrayList<>(fields.keySet()); + String[] lastArgument = args[args.length - 1].split(":", 2); + + //If last argument is an incomplete field value, suggest matching values for that field. + if (lastArgument.length == 2) { + if(fields.containsKey(lastArgument[0])) { + return Arrays.stream(fields.get(lastArgument[0]).get()) + .filter(value -> value.startsWith(lastArgument[1])) + .map(value -> lastArgument[0] + ":" + value) + .collect(Collectors.toList()); + } else { + return Collections.emptyList(); + } + } + + //Remove fields with values in previous args from suggestions + for (String arg : args) { + String[] value = arg.split(":"); + + if (suggestions.contains(value[0]) && value.length == 2) { + suggestions.remove(value[0]); + } + } + + //Suggest remaining fields + return suggestions.stream(). + filter(field -> field.startsWith(args[args.length - 1])) + .map(field -> field + ":") + .collect(Collectors.toList()); + } + public boolean processCommand(DynmapCommandSender sender, String cmd, String commandLabel, String[] args) { if (mapManager == null) { // Initialization faulure sender.sendMessage("Dynmap failed to initialize properly: commands not available"); @@ -1827,6 +1873,10 @@ public class DynmapCore implements DynmapCommonAPI { return getSubcommandSuggestions(sender, cmd, args[0]); } + if (cmd.equalsIgnoreCase("dmap")) { + return dmapcmds.getTabCompletions(sender, args, this); + } + if (!cmd.equalsIgnoreCase("dynmap")) { return Collections.emptyList(); } diff --git a/DynmapCore/src/main/java/org/dynmap/DynmapMapCommands.java b/DynmapCore/src/main/java/org/dynmap/DynmapMapCommands.java index ee487db8..9e4271c9 100644 --- a/DynmapCore/src/main/java/org/dynmap/DynmapMapCommands.java +++ b/DynmapCore/src/main/java/org/dynmap/DynmapMapCommands.java @@ -1,11 +1,16 @@ package org.dynmap; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.function.Supplier; import org.dynmap.common.DynmapCommandSender; import org.dynmap.common.DynmapPlayer; @@ -22,6 +27,76 @@ import org.dynmap.utils.VisibilityLimit; * Handler for world and map edit commands (via /dmap) */ public class DynmapMapCommands { + private Map>> tabCompletions = null; + + /** + * Generates a map of field:value argument tab completion suggestions for every /dmap subcommand + */ + private void initTabCompletions() { + //Static values + String[] emptyValue = new String[]{}; + String[] booleanValue = new String[]{"true", "false"}; + String[] hideStyles = Arrays.stream(HiddenChunkStyle.values()).map(HiddenChunkStyle::getValue) + .toArray(String[]::new); + String[] perspectives = MapManager.mapman.hdmapman.perspectives.keySet().toArray(new String[0]); + String[] shaders = MapManager.mapman.hdmapman.shaders.keySet().toArray(new String[0]); + String[] lightings = MapManager.mapman.hdmapman.lightings.keySet().toArray(new String[0]); + String[] imageFormats = Arrays.stream(MapType.ImageFormat.values()) + .map(MapType.ImageFormat::getID).toArray(String[]::new); + + Supplier emptySupplier = () -> emptyValue; + Supplier booleanSupplier = () -> booleanValue; + Supplier hideStyleSupplier = () -> hideStyles; + Supplier perspectiveSupplier = () -> perspectives; + Supplier shaderSupplier = () -> shaders; + Supplier lightingSupplier = () -> lightings; + Supplier imageFormatSupplier = () -> imageFormats; + + //Arguments for /dmap worldset + Map> worldSetArgs = new LinkedHashMap<>(); + worldSetArgs.put("enabled", booleanSupplier); + worldSetArgs.put("title", emptySupplier); + worldSetArgs.put("order", emptySupplier); + worldSetArgs.put("center", emptySupplier); + worldSetArgs.put("sendposition", booleanSupplier); + worldSetArgs.put("sendhealth", booleanSupplier); + worldSetArgs.put("showborder", booleanSupplier); + worldSetArgs.put("protected", booleanSupplier); + worldSetArgs.put("extrazoomout", emptySupplier); + worldSetArgs.put("tileupdatedelay", emptySupplier); + + //Arguments for /dmap worldaddlimit + Map> worldAddLimitArgs = new LinkedHashMap<>(); + worldAddLimitArgs.put("type", () -> new String[]{"round", "rect"}); + worldAddLimitArgs.put("limittype", () -> new String[]{"visible", "hidden"}); + worldAddLimitArgs.put("style", hideStyleSupplier); + worldAddLimitArgs.put("corner1", emptySupplier); + worldAddLimitArgs.put("corner2", emptySupplier); + worldAddLimitArgs.put("center", emptySupplier); + worldAddLimitArgs.put("radius", emptySupplier); + + //Arguments for /dmap mapadd/mapset + Map> mapSetArgs = new LinkedHashMap<>(); + mapSetArgs.put("title", emptySupplier); + mapSetArgs.put("icon", emptySupplier); + mapSetArgs.put("order", emptySupplier); + mapSetArgs.put("prefix", emptySupplier); + mapSetArgs.put("perspective", perspectiveSupplier); + mapSetArgs.put("shader", shaderSupplier); + mapSetArgs.put("lighting", lightingSupplier); + mapSetArgs.put("img-format", imageFormatSupplier); + mapSetArgs.put("protected", booleanSupplier); + mapSetArgs.put("append-to-world", emptySupplier); + mapSetArgs.put("mapzoomin", emptySupplier); + mapSetArgs.put("mapzoomout", emptySupplier); + mapSetArgs.put("boostzoom", emptySupplier); + mapSetArgs.put("tileupdatedelay", emptySupplier); + + tabCompletions = new HashMap<>(); + tabCompletions.put("worldaddlimit", worldAddLimitArgs); + tabCompletions.put("worldset", worldSetArgs); + tabCompletions.put("mapset", mapSetArgs); //Also used for mapadd + } private boolean checkIfActive(DynmapCore core, DynmapCommandSender sender) { if ((!core.getPauseFullRadiusRenders()) || (!core.getPauseUpdateRenders())) { @@ -94,6 +169,78 @@ public class DynmapMapCommands { } return rslt; } + + public List getTabCompletions(DynmapCommandSender sender, String[] args, DynmapCore core) { + /* Re-parse args - handle doublequotes */ + args = DynmapCore.parseArgs(args, sender); + + if (args == null || args.length <= 1) { + return Collections.emptyList(); + } + + if (tabCompletions == null) { + initTabCompletions(); + } + + String cmd = args[0]; + + if (cmd.equalsIgnoreCase("worldlist") + && core.checkPlayerPermission(sender, "dmap.worldlist")) { + List suggestions = core.getWorldSuggestions(args[args.length - 1]); + suggestions.removeAll(Arrays.asList(args)); //Remove suggestions present in other arguments + + return suggestions; + } else if ((cmd.equalsIgnoreCase("maplist") + && core.checkPlayerPermission(sender, "dmap.maplist")) + || (cmd.equalsIgnoreCase("worldgetlimits") + && core.checkPlayerPermission(sender, "dmap.worldlist"))) { + if (args.length == 2) { + return core.getWorldSuggestions(args[1]); + } + } else if (cmd.equalsIgnoreCase("worldremovelimit") + && core.checkPlayerPermission(sender, "dmap.worldset")) { + if (args.length == 2) { + return core.getWorldSuggestions(args[1]); + } + } else if (cmd.equalsIgnoreCase("worldaddlimit") + && core.checkPlayerPermission(sender, "dmap.worldset")) { + if (args.length == 2) { + return core.getWorldSuggestions(args[1]); + } else { + return core.getFieldValueSuggestions(args, tabCompletions.get("worldaddlimit")); + } + } else if (cmd.equalsIgnoreCase("worldset") + && core.checkPlayerPermission(sender, "dmap.worldset")) { + if (args.length == 2) { + return core.getWorldSuggestions(args[1]); + } else { + return core.getFieldValueSuggestions(args, tabCompletions.get("worldset")); + } + } else if (cmd.equalsIgnoreCase("mapdelete") + && core.checkPlayerPermission(sender, "dmap.mapdelete")) { + if (args.length == 2) { + return core.getMapSuggestions(args[1]); + } + } else if (cmd.equalsIgnoreCase("worldreset") + && core.checkPlayerPermission(sender, "dmap.worldreset")) { + if (args.length == 2) { + return core.getWorldSuggestions(args[1]); + } + } else if (cmd.equalsIgnoreCase("mapset") + && core.checkPlayerPermission(sender, "dmap.mapset")) { + if (args.length == 2) { + return core.getMapSuggestions(args[1]); + } else { + return core.getFieldValueSuggestions(args, tabCompletions.get("mapset")); + } + } else if (cmd.equalsIgnoreCase("mapadd")) { + if (args.length > 2) { + return core.getFieldValueSuggestions(args, tabCompletions.get("mapset")); + } + } + + return Collections.emptyList(); + } private boolean handleWorldList(DynmapCommandSender sender, String[] args, DynmapCore core) { if(!core.checkPlayerPermission(sender, "dmap.worldlist"))