diff --git a/DynmapCore/src/main/java/org/dynmap/DynmapCore.java b/DynmapCore/src/main/java/org/dynmap/DynmapCore.java index 6858882d..62376c72 100644 --- a/DynmapCore/src/main/java/org/dynmap/DynmapCore.java +++ b/DynmapCore/src/main/java/org/dynmap/DynmapCore.java @@ -1877,6 +1877,10 @@ public class DynmapCore implements DynmapCommonAPI { return dmapcmds.getTabCompletions(sender, args, this); } + if (cmd.equalsIgnoreCase("dmarker")) { + return markerapi.getTabCompletions(sender, args, this); + } + if (!cmd.equalsIgnoreCase("dynmap")) { return Collections.emptyList(); } diff --git a/DynmapCore/src/main/java/org/dynmap/markers/impl/MarkerAPIImpl.java b/DynmapCore/src/main/java/org/dynmap/markers/impl/MarkerAPIImpl.java index 9ab12aa6..83382b2f 100644 --- a/DynmapCore/src/main/java/org/dynmap/markers/impl/MarkerAPIImpl.java +++ b/DynmapCore/src/main/java/org/dynmap/markers/impl/MarkerAPIImpl.java @@ -10,8 +10,10 @@ import java.io.IOException; import java.io.InputStream; 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.Map.Entry; @@ -49,6 +51,7 @@ import org.dynmap.markers.PolyLineMarker; import org.dynmap.utils.BufferOutputStream; import org.dynmap.web.Json; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; /** * Implementation class for MarkerAPI - should not be called directly @@ -65,6 +68,8 @@ public class MarkerAPIImpl implements MarkerAPI, Event.Listener { private DynmapCore core; static MarkerAPIImpl api; + private Map>> tabCompletions = null; + /* Built-in icons */ private static final String[] builtin_icons = { "anchor", "bank", "basket", "bed", "beer", "bighouse", "blueflag", "bomb", "bookshelf", "bricks", "bronzemedal", "bronzestar", @@ -420,6 +425,182 @@ public class MarkerAPIImpl implements MarkerAPI, Event.Listener { } }, 0); } + + /** + * Generates a map of field:value argument tab completion suggestions for every /dmarker subcommand + * This is quite long as there are a lot of arguments to deal with, and don't have Java 9 map literals + */ + private void initTabCompletions() { + //Static values + String[] emptyValue = new String[]{}; + String[] booleanValue = new String[]{"true", "false"}; + String[] typeValue = new String[]{"icon", "area", "line", "circle"}; + + Supplier emptySupplier = () -> emptyValue; + Supplier booleanSupplier = () -> booleanValue; + + //Dynamic values + Supplier iconSupplier = () -> markericons.keySet().toArray(new String[0]); + Supplier markerSetSupplier = () -> markersets.keySet().toArray(new String[0]); + Supplier worldSupplier = () -> + core.mapManager.getWorlds().stream().map(DynmapWorld::getName).toArray(String[]::new); + + //Arguments used in multiple commands + Map> labelArg = Collections.singletonMap("label", emptySupplier); + Map> idArg = Collections.singletonMap("id", emptySupplier); + Map> newLabelArg = Collections.singletonMap("newlabel", emptySupplier); + Map> markerSetArg = Collections.singletonMap("set", markerSetSupplier); + Map> newSetArg = Collections.singletonMap("newset", markerSetSupplier); + Map> fileArg = Collections.singletonMap("file", emptySupplier); + + //Arguments used in commands taking a location + Map> locationArgs = new LinkedHashMap<>(); + locationArgs.put("x", emptySupplier); + locationArgs.put("y", emptySupplier); + locationArgs.put("z", emptySupplier); + locationArgs.put("world", worldSupplier); + + //Args shared with all add/update commands + Map> sharedArgs = new LinkedHashMap<>(labelArg); + sharedArgs.putAll(idArg); + + //Args shared with all add/update commands affecting objects visible on the map + Map> mapObjectArgs = new LinkedHashMap<>(sharedArgs); + mapObjectArgs.put("minzoom", emptySupplier); + mapObjectArgs.put("maxzoom", emptySupplier); + + //Args for marker set add/update commands + Map> setArgs = new LinkedHashMap<>(mapObjectArgs); + setArgs.put("prio", emptySupplier); + setArgs.put("hide", booleanSupplier); + setArgs.put("showlabel", booleanSupplier); + setArgs.put("deficon", iconSupplier); + + //Args for marker add/update commands + Map> markerArgs = new LinkedHashMap<>(mapObjectArgs); + markerArgs.putAll(markerSetArg); + markerArgs.put("markup", booleanSupplier); + markerArgs.put("icon", iconSupplier); + markerArgs.putAll(locationArgs); + + //Args for area/line/circle add/update commands + Map> shapeArgs = new LinkedHashMap<>(mapObjectArgs); + shapeArgs.putAll(markerSetArg); + shapeArgs.put("markup", booleanSupplier); + shapeArgs.put("weight", emptySupplier); + shapeArgs.put("color", emptySupplier); + shapeArgs.put("opacity", emptySupplier); + + //Args for area/circle add/update commands + Map> filledShapeArgs = new LinkedHashMap<>(shapeArgs); + filledShapeArgs.put("fillcolor", emptySupplier); + filledShapeArgs.put("fillopacity", emptySupplier); + filledShapeArgs.put("greeting", emptySupplier); + filledShapeArgs.put("greetingsub", emptySupplier); + filledShapeArgs.put("farewell", emptySupplier); + filledShapeArgs.put("farewellsub", emptySupplier); + filledShapeArgs.put("boost", booleanSupplier); + filledShapeArgs.putAll(locationArgs); + + //Args for area add/update commands + Map> areaArgs = new LinkedHashMap<>(filledShapeArgs); + areaArgs.put("ytop", emptySupplier); + areaArgs.put("ybottom", emptySupplier); + + //Args for circle add/update commands + Map> circleArgs = new LinkedHashMap<>(filledShapeArgs); + circleArgs.put("radius", emptySupplier); + circleArgs.put("radiusx", emptySupplier); + circleArgs.put("radiusz", emptySupplier); + + //Args for icon add/update commands + Map> iconArgs = new LinkedHashMap<>(sharedArgs); + iconArgs.putAll(fileArg); + + //Args for updateset command + Map> updateSetArgs = new LinkedHashMap<>(setArgs); + updateSetArgs.putAll(newLabelArg); + + //Args for update (marker) command + Map> updateMarkerArgs = new LinkedHashMap<>(markerArgs); + updateMarkerArgs.putAll(newLabelArg); + updateMarkerArgs.putAll(newSetArg); + + //Args for updateline command + Map> updateLineArgs = new LinkedHashMap<>(shapeArgs); + updateLineArgs.putAll(newLabelArg); + updateLineArgs.putAll(newSetArg); + + //Args for updatearea command + Map> updateAreaArgs = new LinkedHashMap<>(areaArgs); + updateAreaArgs.putAll(newLabelArg); + updateAreaArgs.putAll(newSetArg); + + //Args for updatecircle command + Map> updateCircleArgs = new LinkedHashMap<>(circleArgs); + updateCircleArgs.putAll(newLabelArg); + updateCircleArgs.putAll(newSetArg); + + //Args for updateicon command + Map> updateIconArgs = new LinkedHashMap<>(iconArgs); + updateIconArgs.putAll(newLabelArg); + + //Args for movehere command + Map> moveHereArgs = new LinkedHashMap<>(sharedArgs); + moveHereArgs.putAll(markerSetArg); + + //Args for marker/area/circle/line delete commands + Map> deleteArgs = new LinkedHashMap<>(sharedArgs); + deleteArgs.putAll(markerSetArg); + + //Args for label/desc commands + Map> descArgs = new LinkedHashMap<>(sharedArgs); + descArgs.putAll(markerSetArg); + descArgs.put("type", () -> typeValue); + + //Args for label/desc import commands + Map> importArgs = new LinkedHashMap<>(descArgs); + importArgs.putAll(fileArg); + + //Args for appendesc command + Map> appendArgs = new LinkedHashMap<>(descArgs); + appendArgs.put("desc", emptySupplier); + + tabCompletions = new HashMap<>(); + tabCompletions.put("add", markerArgs); + tabCompletions.put("addicon", iconArgs); + tabCompletions.put("addarea", areaArgs); + tabCompletions.put("addline", shapeArgs); //No unique args + tabCompletions.put("addcircle", circleArgs); + tabCompletions.put("addset", setArgs); + + tabCompletions.put("update", updateMarkerArgs); + tabCompletions.put("updateicon", updateIconArgs); + tabCompletions.put("updatearea", updateAreaArgs); + tabCompletions.put("updateline", updateLineArgs); + tabCompletions.put("updatecircle", updateCircleArgs); + tabCompletions.put("updateset", updateSetArgs); + tabCompletions.put("movehere", moveHereArgs); + + tabCompletions.put("delete", deleteArgs); + tabCompletions.put("deleteicon", sharedArgs); //Doesn't have set: arg + tabCompletions.put("deletearea", deleteArgs); + tabCompletions.put("deleteline", deleteArgs); + tabCompletions.put("deletecircle", deleteArgs); + tabCompletions.put("deleteset", sharedArgs); //Doesn't have set: arg + + tabCompletions.put("list", markerSetArg); + tabCompletions.put("listareas", markerSetArg); + tabCompletions.put("listlines", markerSetArg); + tabCompletions.put("listcircles", markerSetArg); + + tabCompletions.put("getdesc", descArgs); + tabCompletions.put("importdesc", importArgs); + tabCompletions.put("resetdesc", descArgs); + tabCompletions.put("getlabel", descArgs); + tabCompletions.put("importlabel", importArgs); + tabCompletions.put("appenddesc", appendArgs); + } public void scheduleWriteJob() { core.getServer().scheduleServerTask(new DoFileWrites(), 20); @@ -1320,6 +1501,32 @@ public class MarkerAPIImpl implements MarkerAPI, Event.Listener { } } + 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.equals("addcorner") && core.checkPlayerPermission(sender, "marker.addarea")) { + if (args.length == 5) { + return core.getWorldSuggestions(args[4]); + } + } else if (core.checkPlayerPermission(sender, "marker." + cmd) + && tabCompletions.containsKey(cmd)) { + return core.getFieldValueSuggestions(args, tabCompletions.get(cmd)); + } + + return Collections.emptyList(); + } + private static boolean processAddMarker(DynmapCore plugin, DynmapCommandSender sender, String cmd, String commandLabel, String[] args, DynmapPlayer player) { String id, setid, label, iconid, markup; String x, y, z, world, normalized_world;