mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-09-27 13:22:34 +02:00
Merge pull request #3571 from JLyne/tabcomplete
Add tab completions for commands
This commit is contained in:
commit
48ba0b2e41
@ -27,9 +27,12 @@ import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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;
|
||||
|
||||
@ -1117,6 +1120,11 @@ public class DynmapCore implements DynmapCommonAPI {
|
||||
|
||||
/* Parse argument strings : handle quoted strings */
|
||||
public static String[] parseArgs(String[] args, DynmapCommandSender snd) {
|
||||
return parseArgs(args, snd, false);
|
||||
}
|
||||
|
||||
/* Parse argument strings : handle quoted strings */
|
||||
public static String[] parseArgs(String[] args, DynmapCommandSender snd, boolean allowUnclosedQuotes) {
|
||||
ArrayList<String> rslt = new ArrayList<String>();
|
||||
/* Build command line, so we can parse our way - make sure there is trailing space */
|
||||
String cmdline = "";
|
||||
@ -1146,10 +1154,16 @@ public class DynmapCore implements DynmapCommonAPI {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
if(inquote) { /* If still in quote, syntax error */
|
||||
if(inquote) { // If still in quote
|
||||
if(allowUnclosedQuotes) {
|
||||
if(sb.length() > 1) { // Add remaining input without trailing space
|
||||
rslt.add(sb.substring(0, sb.length() - 1));
|
||||
}
|
||||
} else { // Syntax error
|
||||
snd.sendMessage("Error: unclosed doublequote");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return rslt.toArray(new String[rslt.size()]);
|
||||
}
|
||||
|
||||
@ -1352,6 +1366,163 @@ public class DynmapCore implements DynmapCommonAPI {
|
||||
sender.sendMessage(subcmdlist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns tab completion suggestions for subcommands
|
||||
*
|
||||
* @param sender - The command sender requesting the tab completion suggestions
|
||||
* @param cmd - The top level command to suggest for
|
||||
* @param arg - Optional partial subcommand name to filter by
|
||||
* @return List of tab completion suggestions
|
||||
*/
|
||||
List<String> getSubcommandSuggestions(DynmapCommandSender sender, String cmd, String arg) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
|
||||
for (CommandInfo ci : commandinfo) {
|
||||
//TODO: Permission checks
|
||||
if (ci.matches(cmd) && ci.subcmd.startsWith(arg) && !suggestions.contains(ci.subcmd)) {
|
||||
suggestions.add(ci.subcmd);
|
||||
}
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns tab completion suggestions for world names
|
||||
*
|
||||
* @param arg - Partial world name to filter by
|
||||
* @return List of tab completion suggestions
|
||||
*/
|
||||
public List<String> getWorldSuggestions(String arg) {
|
||||
return mapManager.getWorlds().stream()
|
||||
.map(DynmapWorld::getName)
|
||||
.filter(name -> name.startsWith(arg))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns tab completion suggestions for map names of a specific world
|
||||
*
|
||||
* @param worldName - Name of the world
|
||||
* @param mapArg - Partial map name to filter by
|
||||
* @param colonSeparated - Whether to return suggestions in world:map format
|
||||
* @return List of tab completion suggestions
|
||||
*/
|
||||
List<String> getMapSuggestions(String worldName, String mapArg, boolean colonSeparated) {
|
||||
DynmapWorld world = mapManager.getWorld(worldName);
|
||||
|
||||
if (world != null) {
|
||||
//Don't suggest anything if the argument contains a space as the client doesn't handle this well
|
||||
if(mapArg.contains(" ")) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return world.maps.stream()
|
||||
.filter(map -> map.getName().startsWith(mapArg))
|
||||
.map(map -> {
|
||||
if (map.getName().contains(" ")) { //Quote if map name contains a space
|
||||
return "\"" + (colonSeparated ? worldName + ":" + map.getName() : map.getName()) + "\"";
|
||||
} else {
|
||||
return colonSeparated ? worldName + ":" + map.getName() : map.getName();
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns tab completion suggestions for map names without a world name, in world:map format
|
||||
*
|
||||
* @param arg - Partial world:map name to filter by
|
||||
* @return List of tab completion suggestions
|
||||
*/
|
||||
List<String> getMapSuggestions(String arg) {
|
||||
int colon = arg.indexOf(":");
|
||||
final String worldName = (colon >= 0) ? arg.substring(0, colon) : arg;
|
||||
String mapArg = (colon >= 0) ? arg.substring(colon + 1) : null;
|
||||
|
||||
//Don't suggest anything if the argument contains a space as the client doesn't handle this well
|
||||
if(arg.contains(" ")) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (mapArg != null) {
|
||||
return getMapSuggestions(worldName, mapArg, true);
|
||||
}
|
||||
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
|
||||
mapManager.getWorlds().stream()
|
||||
.filter(world -> world.getName().startsWith(worldName))
|
||||
.forEach(world -> {
|
||||
List<String> maps = world.maps.stream()
|
||||
.map(map -> {
|
||||
if (map.getName().contains(" ")) { //Quote if map name contains a space
|
||||
return "\"" + world.getName() + ":" + map.getName() + "\"";
|
||||
} else {
|
||||
return world.getName() + ":" + map.getName();
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
suggestions.addAll(maps);
|
||||
});
|
||||
|
||||
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<String> getFieldValueSuggestions(String[] args, Map<String, Supplier<String[]>> fields) {
|
||||
if (args.length == 0 || fields == null || fields.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> 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])) {
|
||||
//Don't suggest anything if the value contains a space as the client doesn't handle this well
|
||||
if(lastArgument[1].contains(" ")) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Arrays.stream(fields.get(lastArgument[0]).get())
|
||||
.filter(value -> value.startsWith(lastArgument[1]))
|
||||
//Format suggestions as field:value, quoting the value if it contains a space
|
||||
.map(value -> lastArgument[0] + ":" + (value.contains(" ") ? "\"" + value + "\"" : 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");
|
||||
@ -1729,6 +1900,155 @@ public class DynmapCore implements DynmapCommonAPI {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of tab completion suggestions for the given sender, command and command arguments.
|
||||
*
|
||||
* @param sender - The sender of the tab completion, used for permission checks
|
||||
* @param cmd - The top level command being tab completed
|
||||
* @param args - Array of extra command arguments
|
||||
* @return List of tab completion suggestions
|
||||
*/
|
||||
public List<String> getTabCompletions(DynmapCommandSender sender, String cmd, String[] args) {
|
||||
if (mapManager == null || args.length == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (args.length == 1) {
|
||||
return getSubcommandSuggestions(sender, cmd, args[0]);
|
||||
}
|
||||
|
||||
if (cmd.equalsIgnoreCase("dmap")) {
|
||||
return dmapcmds.getTabCompletions(sender, args, this);
|
||||
}
|
||||
|
||||
if (cmd.equalsIgnoreCase("dmarker")) {
|
||||
return markerapi.getTabCompletions(sender, args, this);
|
||||
}
|
||||
|
||||
if (cmd.equalsIgnoreCase("dynmapexp")) {
|
||||
return dynmapexpcmds.getTabCompletions(sender, args, this);
|
||||
}
|
||||
|
||||
if (!cmd.equalsIgnoreCase("dynmap")) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/* Re-parse args - handle double quotes */
|
||||
args = parseArgs(args, sender, true);
|
||||
|
||||
if (args == null || args.length <= 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String subcommand = args[0];
|
||||
DynmapPlayer player = null;
|
||||
if (sender instanceof DynmapPlayer) {
|
||||
player = (DynmapPlayer) sender;
|
||||
}
|
||||
|
||||
if (subcommand.equals("radiusrender") && checkPlayerPermission(sender, "radiusrender")) {
|
||||
if(args.length == 2) { // /dynmap radiusrender *<world>* <x> <z> <radius> <map>
|
||||
return getWorldSuggestions(args[1]);
|
||||
} if(args.length == 3 && player != null) { // /dynmap radiusrender <radius> *<mapname>*
|
||||
Scanner sc = new Scanner(args[1]);
|
||||
|
||||
if(sc.hasNextInt(10)) { //Only show map suggestions if a number was entered before
|
||||
return getMapSuggestions(player.getLocation().world, args[2], false);
|
||||
}
|
||||
} else if(args.length == 6) { // /dynmap radiusrender <world> <x> <z> <radius> *<map>*
|
||||
return getMapSuggestions(args[1], args[5], false);
|
||||
}
|
||||
} else if (subcommand.equals("updaterender") && checkPlayerPermission(sender, "updaterender")) {
|
||||
if(args.length == 2) { // /dynmap updaterender *<world>* <x> <z> <map>/*<map>*
|
||||
List<String> suggestions = getWorldSuggestions(args[1]);
|
||||
|
||||
if(player != null) {
|
||||
suggestions.addAll(getMapSuggestions(player.getLocation().world, args[1], false));
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
} else if(args.length == 5) { // /dynmap updaterender <world> <x> <z> *<map>*
|
||||
return getMapSuggestions(args[1], args[4], false);
|
||||
}
|
||||
} else if (subcommand.equals("hide") && checkPlayerPermission(sender, "hide.others")) {
|
||||
if(args.length == 2) { // /dynmap hide *<player>*
|
||||
final String arg = args[1];
|
||||
return playerList.getVisiblePlayers().stream()
|
||||
.map(DynmapPlayer::getName)
|
||||
.filter(name -> name.startsWith(arg))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
} else if (subcommand.equals("show") && checkPlayerPermission(sender, "show.others")) {
|
||||
if(args.length == 2) { // /dynmap show *<player>*
|
||||
final String arg = args[1];
|
||||
return playerList.getHiddenPlayers().stream()
|
||||
.map(DynmapPlayer::getName)
|
||||
.filter(name -> name.startsWith(arg))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
} else if (subcommand.equals("fullrender") && checkPlayerPermission(sender, "fullrender")) {
|
||||
List<String> suggestions = getWorldSuggestions(args[args.length - 1]); //World suggestions
|
||||
suggestions.addAll(getMapSuggestions(args[args.length - 1])); //world:map suggestions
|
||||
|
||||
//Remove suggestions present in other arguments
|
||||
for (String arg : args) {
|
||||
suggestions.remove(arg.contains(" ") ? "\"" + arg + "\"" : arg);
|
||||
}
|
||||
|
||||
//Add resume if previous argument wasn't resume
|
||||
if ("resume".startsWith(args[args.length - 1])
|
||||
&& (args.length == 2 || !args[args.length - 2].equals("resume"))) {
|
||||
suggestions.add("resume");
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
} else if ((subcommand.equals("cancelrender") && checkPlayerPermission(sender, "cancelrender"))
|
||||
|| (subcommand.equals("purgequeue") && checkPlayerPermission(sender, "purgequeue"))) {
|
||||
List<String> suggestions = getWorldSuggestions(args[args.length - 1]);
|
||||
suggestions.removeAll(Arrays.asList(args)); //Remove worlds present in other arguments
|
||||
|
||||
return suggestions;
|
||||
} else if (subcommand.equals("purgemap") && checkPlayerPermission(sender, "purgemap")) {
|
||||
if (args.length == 2) { // /dynmap purgemap *<world>* <map>
|
||||
return getWorldSuggestions(args[1]);
|
||||
} else if (args.length == 3) { // /dynmap purgemap <world> *<map>*
|
||||
return getMapSuggestions(args[1], args[2], false);
|
||||
}
|
||||
} else if ((subcommand.equals("purgeworld") && checkPlayerPermission(sender, "purgeworld"))
|
||||
|| (subcommand.equals("stats") && checkPlayerPermission(sender, "stats"))
|
||||
|| (subcommand.equals("resetstats") && checkPlayerPermission(sender, "resetstats"))) {
|
||||
if (args.length == 2) {
|
||||
return getWorldSuggestions(args[1]);
|
||||
}
|
||||
} else if (subcommand.equals("pause") && checkPlayerPermission(sender, "pause")) {
|
||||
List<String> suggestions = Arrays.asList("full", "update", "all", "none");
|
||||
|
||||
if (args.length == 2) {
|
||||
final String arg = args[1];
|
||||
return suggestions.stream().filter(suggestion -> suggestion.startsWith(arg))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
} else if((subcommand.equals("ips-for-id") && checkPlayerPermission(sender, "ips-for-id"))
|
||||
|| (subcommand.equals("add-id-for-ip") && checkPlayerPermission(sender, "add-id-for-ip"))
|
||||
|| (subcommand.equals("del-id-for-ip") && checkPlayerPermission(sender, "del-id-for-ip"))
|
||||
|| (subcommand.equals("webregister") && checkPlayerPermission(sender, "webregister.other"))) {
|
||||
if(args.length == 2) {
|
||||
final String arg = args[1];
|
||||
return Arrays.stream(playerList.getOnlinePlayers())
|
||||
.map(DynmapPlayer::getName)
|
||||
.filter(name -> name.startsWith(arg))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
} else if(subcommand.equals("help")) {
|
||||
if(args.length == 2) {
|
||||
return getSubcommandSuggestions(sender, "dynmap", args[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public boolean checkPlayerPermission(DynmapCommandSender sender, String permission) {
|
||||
if (!(sender instanceof DynmapPlayer) || sender.isOp()) {
|
||||
return true;
|
||||
|
@ -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<String, Map<String, Supplier<String[]>>> 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<String[]> emptySupplier = () -> emptyValue;
|
||||
Supplier<String[]> booleanSupplier = () -> booleanValue;
|
||||
Supplier<String[]> hideStyleSupplier = () -> hideStyles;
|
||||
Supplier<String[]> perspectiveSupplier = () -> perspectives;
|
||||
Supplier<String[]> shaderSupplier = () -> shaders;
|
||||
Supplier<String[]> lightingSupplier = () -> lightings;
|
||||
Supplier<String[]> imageFormatSupplier = () -> imageFormats;
|
||||
|
||||
//Arguments for /dmap worldset
|
||||
Map<String, Supplier<String[]>> 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<String, Supplier<String[]>> 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<String, Supplier<String[]>> 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())) {
|
||||
@ -95,6 +170,78 @@ public class DynmapMapCommands {
|
||||
return rslt;
|
||||
}
|
||||
|
||||
public List<String> getTabCompletions(DynmapCommandSender sender, String[] args, DynmapCore core) {
|
||||
/* Re-parse args - handle doublequotes */
|
||||
args = DynmapCore.parseArgs(args, sender, true);
|
||||
|
||||
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<String> 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"))
|
||||
return true;
|
||||
|
@ -1,7 +1,13 @@
|
||||
package org.dynmap.exporter;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.dynmap.DynmapCore;
|
||||
import org.dynmap.DynmapLocation;
|
||||
@ -91,6 +97,53 @@ public class DynmapExpCommands {
|
||||
return rslt;
|
||||
}
|
||||
|
||||
public List<String> getTabCompletions(DynmapCommandSender sender, String[] args, DynmapCore core) {
|
||||
/* Re-parse args - handle doublequotes */
|
||||
args = DynmapCore.parseArgs(args, sender, true);
|
||||
|
||||
if (args == null || args.length <= 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String cmd = args[0];
|
||||
|
||||
if(cmd.equalsIgnoreCase("set")) {
|
||||
List<String> keys = new ArrayList<>(
|
||||
Arrays.asList("x0", "x1", "y0", "y1", "z0", "z1", "world", "shader", "byChunk",
|
||||
"byBlockID", "byBlockIDData", "byTexture"));
|
||||
|
||||
if (args.length % 2 == 0) { // Args contain only complete key value argument pairs (plus subcommand)
|
||||
// Remove previous used keys
|
||||
for (int i = 1; i < args.length; i += 2) {
|
||||
keys.remove(args[i]);
|
||||
}
|
||||
|
||||
return keys;
|
||||
} else { // Incomplete key value argument pair, suggest values
|
||||
final String lastKey = args[args.length - 2];
|
||||
final String lastValue = args[args.length - 1];
|
||||
|
||||
switch(lastKey) {
|
||||
case "world":
|
||||
return core.getWorldSuggestions(lastValue);
|
||||
case "shader":
|
||||
return MapManager.mapman.hdmapman.shaders.keySet().stream()
|
||||
.filter(value -> value.startsWith(lastValue))
|
||||
.collect(Collectors.toList());
|
||||
case "byChunk":
|
||||
case "byBlockID":
|
||||
case "byBlockIDData":
|
||||
case "byTexture":
|
||||
return Stream.of("true", "false")
|
||||
.filter(value -> value.startsWith(lastValue))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private boolean handleInfo(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
|
||||
sender.sendMessage(String.format("Bounds: <%s,%s,%s> - <%s,%s,%s> on world '%s'", val(ctx.xmin), val(ctx.ymin), val(ctx.zmin),
|
||||
val(ctx.xmax), val(ctx.ymax), val(ctx.zmax), ctx.world));
|
||||
|
@ -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<DynmapWorld> {
|
||||
private DynmapCore core;
|
||||
static MarkerAPIImpl api;
|
||||
|
||||
private Map<String, Map<String, Supplier<String[]>>> tabCompletions = null;
|
||||
|
||||
/* Built-in icons */
|
||||
private static final String[] builtin_icons = {
|
||||
"anchor", "bank", "basket", "bed", "beer", "bighouse", "blueflag", "bomb", "bookshelf", "bricks", "bronzemedal", "bronzestar",
|
||||
@ -421,6 +426,182 @@ public class MarkerAPIImpl implements MarkerAPI, Event.Listener<DynmapWorld> {
|
||||
}, 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<String[]> emptySupplier = () -> emptyValue;
|
||||
Supplier<String[]> booleanSupplier = () -> booleanValue;
|
||||
|
||||
//Dynamic values
|
||||
Supplier<String[]> iconSupplier = () -> markericons.keySet().toArray(new String[0]);
|
||||
Supplier<String[]> markerSetSupplier = () -> markersets.keySet().toArray(new String[0]);
|
||||
Supplier<String[]> worldSupplier = () ->
|
||||
core.mapManager.getWorlds().stream().map(DynmapWorld::getName).toArray(String[]::new);
|
||||
|
||||
//Arguments used in multiple commands
|
||||
Map<String, Supplier<String[]>> labelArg = Collections.singletonMap("label", emptySupplier);
|
||||
Map<String, Supplier<String[]>> idArg = Collections.singletonMap("id", emptySupplier);
|
||||
Map<String, Supplier<String[]>> newLabelArg = Collections.singletonMap("newlabel", emptySupplier);
|
||||
Map<String, Supplier<String[]>> markerSetArg = Collections.singletonMap("set", markerSetSupplier);
|
||||
Map<String, Supplier<String[]>> newSetArg = Collections.singletonMap("newset", markerSetSupplier);
|
||||
Map<String, Supplier<String[]>> fileArg = Collections.singletonMap("file", emptySupplier);
|
||||
|
||||
//Arguments used in commands taking a location
|
||||
Map<String, Supplier<String[]>> 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<String, Supplier<String[]>> sharedArgs = new LinkedHashMap<>(labelArg);
|
||||
sharedArgs.putAll(idArg);
|
||||
|
||||
//Args shared with all add/update commands affecting objects visible on the map
|
||||
Map<String, Supplier<String[]>> mapObjectArgs = new LinkedHashMap<>(sharedArgs);
|
||||
mapObjectArgs.put("minzoom", emptySupplier);
|
||||
mapObjectArgs.put("maxzoom", emptySupplier);
|
||||
|
||||
//Args for marker set add/update commands
|
||||
Map<String, Supplier<String[]>> 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<String, Supplier<String[]>> 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<String, Supplier<String[]>> 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<String, Supplier<String[]>> 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<String, Supplier<String[]>> areaArgs = new LinkedHashMap<>(filledShapeArgs);
|
||||
areaArgs.put("ytop", emptySupplier);
|
||||
areaArgs.put("ybottom", emptySupplier);
|
||||
|
||||
//Args for circle add/update commands
|
||||
Map<String, Supplier<String[]>> circleArgs = new LinkedHashMap<>(filledShapeArgs);
|
||||
circleArgs.put("radius", emptySupplier);
|
||||
circleArgs.put("radiusx", emptySupplier);
|
||||
circleArgs.put("radiusz", emptySupplier);
|
||||
|
||||
//Args for icon add/update commands
|
||||
Map<String, Supplier<String[]>> iconArgs = new LinkedHashMap<>(sharedArgs);
|
||||
iconArgs.putAll(fileArg);
|
||||
|
||||
//Args for updateset command
|
||||
Map<String, Supplier<String[]>> updateSetArgs = new LinkedHashMap<>(setArgs);
|
||||
updateSetArgs.putAll(newLabelArg);
|
||||
|
||||
//Args for update (marker) command
|
||||
Map<String, Supplier<String[]>> updateMarkerArgs = new LinkedHashMap<>(markerArgs);
|
||||
updateMarkerArgs.putAll(newLabelArg);
|
||||
updateMarkerArgs.putAll(newSetArg);
|
||||
|
||||
//Args for updateline command
|
||||
Map<String, Supplier<String[]>> updateLineArgs = new LinkedHashMap<>(shapeArgs);
|
||||
updateLineArgs.putAll(newLabelArg);
|
||||
updateLineArgs.putAll(newSetArg);
|
||||
|
||||
//Args for updatearea command
|
||||
Map<String, Supplier<String[]>> updateAreaArgs = new LinkedHashMap<>(areaArgs);
|
||||
updateAreaArgs.putAll(newLabelArg);
|
||||
updateAreaArgs.putAll(newSetArg);
|
||||
|
||||
//Args for updatecircle command
|
||||
Map<String, Supplier<String[]>> updateCircleArgs = new LinkedHashMap<>(circleArgs);
|
||||
updateCircleArgs.putAll(newLabelArg);
|
||||
updateCircleArgs.putAll(newSetArg);
|
||||
|
||||
//Args for updateicon command
|
||||
Map<String, Supplier<String[]>> updateIconArgs = new LinkedHashMap<>(iconArgs);
|
||||
updateIconArgs.putAll(newLabelArg);
|
||||
|
||||
//Args for movehere command
|
||||
Map<String, Supplier<String[]>> moveHereArgs = new LinkedHashMap<>(sharedArgs);
|
||||
moveHereArgs.putAll(markerSetArg);
|
||||
|
||||
//Args for marker/area/circle/line delete commands
|
||||
Map<String, Supplier<String[]>> deleteArgs = new LinkedHashMap<>(sharedArgs);
|
||||
deleteArgs.putAll(markerSetArg);
|
||||
|
||||
//Args for label/desc commands
|
||||
Map<String, Supplier<String[]>> descArgs = new LinkedHashMap<>(sharedArgs);
|
||||
descArgs.putAll(markerSetArg);
|
||||
descArgs.put("type", () -> typeValue);
|
||||
|
||||
//Args for label/desc import commands
|
||||
Map<String, Supplier<String[]>> importArgs = new LinkedHashMap<>(descArgs);
|
||||
importArgs.putAll(fileArg);
|
||||
|
||||
//Args for appendesc command
|
||||
Map<String, Supplier<String[]>> 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<DynmapWorld> {
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getTabCompletions(DynmapCommandSender sender, String[] args, DynmapCore core) {
|
||||
/* Re-parse args - handle doublequotes */
|
||||
args = DynmapCore.parseArgs(args, sender, true);
|
||||
|
||||
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;
|
||||
|
@ -4,6 +4,7 @@ import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
@ -1103,6 +1104,21 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command cmd, String alias, String[] args) {
|
||||
DynmapCommandSender dsender;
|
||||
if(sender instanceof Player) {
|
||||
dsender = new BukkitPlayer((Player)sender);
|
||||
}
|
||||
else {
|
||||
dsender = new BukkitCommandSender(sender);
|
||||
}
|
||||
|
||||
if (core != null)
|
||||
return core.getTabCompletions(dsender, cmd.getName(), args);
|
||||
else
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MarkerAPI getMarkerAPI() {
|
||||
|
Loading…
Reference in New Issue
Block a user