diff --git a/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java b/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java index 7487cd52e..0f0633491 100644 --- a/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java +++ b/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java @@ -19,9 +19,19 @@ package com.sk89q.worldedit.command; -import com.sk89q.minecraft.util.commands.*; -import com.sk89q.worldedit.*; +import com.google.common.base.Joiner; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.minecraft.util.commands.Logging; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.EntityType; +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.LocalWorld.KillFlags; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; @@ -29,16 +39,23 @@ import com.sk89q.worldedit.patterns.Pattern; import com.sk89q.worldedit.patterns.SingleBlockPattern; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.command.CommandCallable; import com.sk89q.worldedit.util.command.CommandMapping; import com.sk89q.worldedit.util.command.Description; import com.sk89q.worldedit.util.command.Dispatcher; +import com.sk89q.worldedit.util.command.PrimaryAliasComparator; import com.sk89q.worldedit.util.command.parametric.Optional; +import com.sk89q.worldedit.util.formatting.Cmd; +import com.sk89q.worldedit.util.formatting.ColorCodeBuilder; +import com.sk89q.worldedit.util.formatting.CommandListBox; +import com.sk89q.worldedit.util.formatting.Style; +import com.sk89q.worldedit.util.formatting.StyledFragment; import com.sk89q.worldedit.world.World; -import java.util.Comparator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; import static com.sk89q.minecraft.util.commands.Logging.LogMode.PLACEMENT; @@ -497,59 +514,158 @@ public class UtilityCommands { help(args, we, actor); } - public static void help(CommandContext args, WorldEdit we, Actor actor) { - final Dispatcher dispatcher = we.getPlatformManager().getCommandManager().getDispatcher(); + private static CommandMapping detectCommand(Dispatcher dispatcher, String command, boolean isRootLevel) { + CommandMapping mapping; - if (args.argsLength() == 0) { - SortedSet commands = new TreeSet(new Comparator() { - @Override - public int compare(String o1, String o2) { - final int ret = o1.replaceAll("/", "").compareToIgnoreCase(o2.replaceAll("/", "")); - if (ret == 0) { - return o1.compareToIgnoreCase(o2); - } - return ret; - } - }); - commands.addAll(dispatcher.getPrimaryAliases()); + // First try the command as entered + mapping = dispatcher.get(command); + if (mapping != null) { + return mapping; + } - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (String command : commands) { - if (!first) { - sb.append(", "); - } - - sb.append('/'); - sb.append(command); - first = false; + // Then if we're looking at root commands and the user didn't use + // any slashes, let's try double slashes and then single slashes. + // However, be aware that there exists different single slash + // and double slash commands in WorldEdit + if (isRootLevel && !command.contains("/")) { + mapping = dispatcher.get("//" + command); + if (mapping != null) { + return mapping; } - actor.print(sb.toString()); - - return; + mapping = dispatcher.get("/" + command); + if (mapping != null) { + return mapping; + } } - String command = args.getJoinedStrings(0).toLowerCase().replaceAll("^/", ""); - CommandMapping mapping = dispatcher.get(command); + return null; + } - if (mapping == null) { - actor.printError("Unknown command '" + command + "'."); - return; + public static void help(CommandContext args, WorldEdit we, Actor actor) { + CommandCallable callable = we.getPlatformManager().getCommandManager().getDispatcher(); + + int page = 0; + final int perPage = actor instanceof Player ? 8 : 20; // More pages for console + int effectiveLength = args.argsLength(); + + // Detect page from args + try { + if (args.argsLength() > 0) { + page = args.getInteger(args.argsLength() - 1); + if (page <= 0) { + page = 1; + } else { + page--; + } + + effectiveLength--; + } + } catch (NumberFormatException ignored) { } - Description description = mapping.getDescription(); + boolean isRootLevel = true; + List visited = new ArrayList(); - if (description.getUsage() != null) { - actor.printDebug("Usage: " + description.getUsage()); + // Drill down to the command + for (int i = 0; i < effectiveLength; i++) { + String command = args.getString(i); + + if (callable instanceof Dispatcher) { + // Chop off the beginning / if we're are the root level + if (isRootLevel && command.length() > 1 && command.charAt(0) == '/') { + command = command.substring(1); + } + + CommandMapping mapping = detectCommand((Dispatcher) callable, command, isRootLevel); + if (mapping != null) { + callable = mapping.getCallable(); + } else { + if (isRootLevel) { + actor.printError(String.format("The command '%s' could not be found.", args.getString(i))); + return; + } else { + actor.printError(String.format("The sub-command '%s' under '%s' could not be found.", + command, Joiner.on(" ").join(visited))); + return; + } + } + + visited.add(args.getString(i)); + isRootLevel = false; + } else { + actor.printError(String.format("'%s' has no sub-commands. (Maybe '%s' is for a parameter?)", + Joiner.on(" ").join(visited), command)); + return; + } } - if (description.getHelp() != null) { - actor.print(description.getHelp()); - } else if (description.getShortDescription() != null) { - actor.print(description.getShortDescription()); + // Create the message + if (callable instanceof Dispatcher) { + Dispatcher dispatcher = (Dispatcher) callable; + + // Get a list of aliases + List aliases = new ArrayList(dispatcher.getCommands()); + Collections.sort(aliases, PrimaryAliasComparator.INSTANCE); + + // Calculate pagination + int offset = perPage * page; + int pageTotal = (int) Math.ceil(aliases.size() / (double) perPage); + + // Box + CommandListBox box = new CommandListBox(String.format("Help: page %d/%d ", page + 1, pageTotal)); + StyledFragment contents = box.getContents(); + StyledFragment tip = contents.createFragment(Style.GRAY); + + if (offset >= aliases.size()) { + tip.createFragment(Style.RED).append(String.format("There is no page %d (total number of pages is %d).", page + 1, pageTotal)).newLine(); + } else { + List list = aliases.subList(offset, Math.min(offset + perPage, aliases.size())); + + tip.append("Type "); + tip.append(new Cmd().append("//help ").append(" []")); + tip.append(" for more information.").newLine(); + + // Add each command + for (CommandMapping mapping : list) { + StringBuilder builder = new StringBuilder(); + if (isRootLevel) { + builder.append("/"); + } + if (!visited.isEmpty()) { + builder.append(Joiner.on(" ").join(visited)); + builder.append(" "); + } + builder.append(mapping.getPrimaryAlias()); + box.appendCommand(builder.toString(), mapping.getDescription().getShortDescription()); + } + } + + actor.printRaw(ColorCodeBuilder.asColorCodes(box)); } else { - actor.print("No further help is available."); + CommandListBox box = new CommandListBox(String.format("Help: %s", Joiner.on(" ").join(visited))); + StyledFragment contents = box.getContents(); + + Description description = callable.getDescription(); + + if (description.getUsage() != null) { + contents.createFragment(Style.YELLOW).append("Usage: "); + contents.append(description.getUsage()); + } else { + contents.createFragment(Style.GRAY).append("Usage information is not available."); + } + + contents.newLine(); + + if (description.getHelp() != null) { + contents.createFragment(Style.YELLOW_DARK).append(description.getHelp()); + } else if (description.getShortDescription() != null) { + contents.createFragment(Style.YELLOW_DARK).append(description.getShortDescription()); + } else { + contents.createFragment(Style.GRAY).append("No further help is available."); + } + + actor.printRaw(ColorCodeBuilder.asColorCodes(box)); } } } diff --git a/src/main/java/com/sk89q/worldedit/util/command/PrimaryAliasComparator.java b/src/main/java/com/sk89q/worldedit/util/command/PrimaryAliasComparator.java new file mode 100644 index 000000000..bcc4197ff --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/command/PrimaryAliasComparator.java @@ -0,0 +1,43 @@ +/* + * 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.util.command; + +import java.util.Comparator; + +/** + * Compares the primary aliases of two {@link CommandMapping} using + * {@link String#compareTo(String)}. + */ +public final class PrimaryAliasComparator implements Comparator { + + /** + * An instance of this class. + */ + public static final PrimaryAliasComparator INSTANCE = new PrimaryAliasComparator(); + + private PrimaryAliasComparator() { + } + + @Override + public int compare(CommandMapping o1, CommandMapping o2) { + return o1.getPrimaryAlias().compareTo(o2.getPrimaryAlias()); + } + +} diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/Cmd.java b/src/main/java/com/sk89q/worldedit/util/formatting/Cmd.java new file mode 100644 index 000000000..c940b73fa --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/formatting/Cmd.java @@ -0,0 +1,34 @@ +/* + * 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.util.formatting; + +/** + * Represents a fragment representing a command that is to be typed. + */ +public class Cmd extends StyledFragment { + + /** + * Create a new instance. + */ + public Cmd() { + super(Style.CYAN); + } + +}