diff --git a/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java b/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java index 31900a9d2..dd3fa00e2 100644 --- a/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java +++ b/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java @@ -145,7 +145,7 @@ public class CommandContext { suggestionContext = SuggestionContext.hangingValue(); // Not a flag? - if (arg.charAt(0) != '-' || arg.length() == 1 || !arg.matches("^-[a-zA-Z]+$")) { + if (arg.charAt(0) != '-' || arg.length() == 1 || !arg.matches("^-[a-zA-Z\\?]+$")) { if (!isHanging) { suggestionContext = SuggestionContext.lastValue(); } diff --git a/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java b/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java index eae94c75f..b6e30825f 100644 --- a/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java +++ b/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java @@ -66,7 +66,7 @@ public class CommandException extends Exception { } builder.append(spacedSuffix); } - return builder.toString(); + return builder.toString().trim(); } } diff --git a/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java b/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java index e231c1c2e..ce43904b9 100644 --- a/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java +++ b/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java @@ -643,8 +643,7 @@ public class UtilityCommands { actor.printRaw(ColorCodeBuilder.asColorCodes(box)); } else { - String title = String.format("Help: %s", Joiner.on(" ").join(visited)); - CommandUsageBox box = new CommandUsageBox(callable.getDescription(), title); + CommandUsageBox box = new CommandUsageBox(callable, Joiner.on(" ").join(visited)); actor.printRaw(ColorCodeBuilder.asColorCodes(box)); } } diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java b/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java index cc9f26556..56fc1754b 100644 --- a/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java +++ b/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java @@ -42,6 +42,8 @@ import com.sk89q.worldedit.util.command.fluent.CommandGraph; import com.sk89q.worldedit.util.command.parametric.LegacyCommandsHandler; import com.sk89q.worldedit.util.command.parametric.ParametricBuilder; import com.sk89q.worldedit.util.eventbus.Subscribe; +import com.sk89q.worldedit.util.formatting.ColorCodeBuilder; +import com.sk89q.worldedit.util.formatting.components.CommandUsageBox; import com.sk89q.worldedit.util.logging.DynamicStreamHandler; import com.sk89q.worldedit.util.logging.LogFormat; @@ -220,7 +222,17 @@ public final class CommandManager { } catch (CommandPermissionsException e) { actor.printError("You don't have permission to do this."); } catch (InvalidUsageException e) { - actor.printError(e.getMessage() + "\nUsage: " + e.getUsage("/")); + if (e.isFullUsageSuggested()) { + actor.printRaw(ColorCodeBuilder.asColorCodes(new CommandUsageBox(e.getCommand(), e.toStackString("/", ""), locals))); + String message = e.getMessage(); + if (message != null) { + actor.printError(message); + } + } else { + String message = e.getMessage(); + actor.printError(message != null ? message : "The command was not used properly (no more help available)."); + actor.printError(e.getUsage("/")); + } } catch (WrappedCommandException e) { Throwable t = e.getCause(); actor.printError("Please report this error: [See console]"); diff --git a/src/main/java/com/sk89q/worldedit/util/command/InvalidUsageException.java b/src/main/java/com/sk89q/worldedit/util/command/InvalidUsageException.java index a97ce4d2f..56195cdf2 100644 --- a/src/main/java/com/sk89q/worldedit/util/command/InvalidUsageException.java +++ b/src/main/java/com/sk89q/worldedit/util/command/InvalidUsageException.java @@ -21,29 +21,46 @@ package com.sk89q.worldedit.util.command; import com.sk89q.minecraft.util.commands.CommandException; +import static com.google.common.base.Preconditions.checkNotNull; + /** * Thrown when a command is not used properly. */ public class InvalidUsageException extends CommandException { private static final long serialVersionUID = -3222004168669490390L; - private final Description description; + private final CommandCallable command; + private final boolean fullUsageSuggested; - public InvalidUsageException(Description description) { - this.description = description; + public InvalidUsageException(CommandCallable command) { + this(null, command); } - public InvalidUsageException(String message, Description description) { + public InvalidUsageException(String message, CommandCallable command) { + this(message, command, false); + } + + public InvalidUsageException(String message, CommandCallable command, boolean fullUsageSuggested) { super(message); - this.description = description; + checkNotNull(command); + this.command = command; + this.fullUsageSuggested = fullUsageSuggested; } - public Description getDescription() { - return description; + public CommandCallable getCommand() { + return command; } public String getUsage(String prefix) { - return toStackString(prefix, getDescription().getUsage()); + return toStackString(prefix, command.getDescription().getUsage()); } + /** + * Return whether the full usage of the command should be shown. + * + * @return show full usage + */ + public boolean isFullUsageSuggested() { + return fullUsageSuggested; + } } diff --git a/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java b/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java index e4733a4f0..8bb7d52c5 100644 --- a/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java +++ b/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java @@ -20,12 +20,20 @@ package com.sk89q.worldedit.util.command; import com.google.common.base.Joiner; -import com.sk89q.minecraft.util.commands.*; -import com.sk89q.worldedit.util.formatting.ColorCodeBuilder; -import com.sk89q.worldedit.util.formatting.components.CommandListBox; -import com.sk89q.worldedit.util.formatting.StyledFragment; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandLocals; +import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import com.sk89q.minecraft.util.commands.WrappedCommandException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * A simple implementation of {@link Dispatcher}. @@ -109,7 +117,7 @@ public class SimpleDispatcher implements Dispatcher { Set aliases = getPrimaryAliases(); if (aliases.isEmpty()) { - throw new InvalidUsageException("This command has no sub-commands.", getDescription()); + throw new InvalidUsageException("This command has no sub-commands.", this); } else if (split.length > 0) { String subCommand = split[0]; String subArguments = Joiner.on(" ").join(Arrays.copyOfRange(split, 1, split.length)); @@ -132,7 +140,7 @@ public class SimpleDispatcher implements Dispatcher { } - throw new InvalidUsageException(ColorCodeBuilder.asColorCodes(getSubcommandList(locals, parentCommands)), getDescription()); + throw new InvalidUsageException("Please choose a sub-command.", this, true); } @Override @@ -185,16 +193,4 @@ public class SimpleDispatcher implements Dispatcher { return false; } - private StyledFragment getSubcommandList(CommandLocals locals, String[] parentCommands) { - CommandListBox box = new CommandListBox("Subcommands"); - String prefix = parentCommands.length > 0 ? "/" + Joiner.on(" ").join(parentCommands) + " " : ""; - for (CommandMapping mapping : getCommands()) { - if (mapping.getCallable().testPermission(locals)) { - box.appendCommand(prefix + mapping.getPrimaryAlias(), mapping.getDescription().getShortDescription()); - } - } - - return box; - } - } diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java index 9cbecc03e..b98aeb00f 100644 --- a/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java +++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java @@ -19,15 +19,32 @@ package com.sk89q.worldedit.util.command.parametric; -import com.sk89q.minecraft.util.commands.*; -import com.sk89q.worldedit.util.command.*; +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.CommandException; +import com.sk89q.minecraft.util.commands.CommandLocals; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import com.sk89q.minecraft.util.commands.WrappedCommandException; +import com.sk89q.worldedit.util.command.CommandCallable; +import com.sk89q.worldedit.util.command.InvalidUsageException; +import com.sk89q.worldedit.util.command.MissingParameterException; +import com.sk89q.worldedit.util.command.Parameter; +import com.sk89q.worldedit.util.command.SimpleDescription; +import com.sk89q.worldedit.util.command.UnconsumedParameterException; import com.sk89q.worldedit.util.command.binding.Switch; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * The implementation of a {@link CommandCallable} for the {@link ParametricBuilder}. @@ -169,6 +186,12 @@ class ParametricCallable implements CommandCallable { String[] split = CommandContext.split(calledCommand + " " + stringArguments); CommandContext context = new CommandContext(split, getValueFlags(), false, locals); + // Provide help if -? is specified + if (context.hasFlag('?')) { + String title = Joiner.on(" ").join(parentCommands); + throw new InvalidUsageException(null, this, true); + } + Object[] args = new Object[parameters.length]; ContextArgumentStack arguments = new ContextArgumentStack(context); ParameterData parameter = null; @@ -221,14 +244,14 @@ class ParametricCallable implements CommandCallable { handler.postInvoke(handler, method, parameters, args, context); } } catch (MissingParameterException e) { - throw new InvalidUsageException("Too few parameters!", getDescription()); + throw new InvalidUsageException("Too few parameters!", this); } catch (UnconsumedParameterException e) { - throw new InvalidUsageException("Too many parameters! Unused parameters: " + e.getUnconsumed(), getDescription()); + throw new InvalidUsageException("Too many parameters! Unused parameters: " + e.getUnconsumed(), this); } catch (ParameterException e) { assert parameter != null; String name = parameter.getName(); - throw new InvalidUsageException("For parameter '" + name + "': " + e.getMessage(), getDescription()); + throw new InvalidUsageException("For parameter '" + name + "': " + e.getMessage(), this); } catch (InvocationTargetException e) { for (ExceptionConverter converter : builder.getExceptionConverters()) { converter.convert(e.getCause()); diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/components/CommandUsageBox.java b/src/main/java/com/sk89q/worldedit/util/formatting/components/CommandUsageBox.java index d14683e09..4332c8efb 100644 --- a/src/main/java/com/sk89q/worldedit/util/formatting/components/CommandUsageBox.java +++ b/src/main/java/com/sk89q/worldedit/util/formatting/components/CommandUsageBox.java @@ -19,56 +19,84 @@ package com.sk89q.worldedit.util.formatting.components; +import com.sk89q.minecraft.util.commands.CommandLocals; +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.formatting.Style; +import com.sk89q.worldedit.util.formatting.StyledFragment; + +import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; /** * A box to describe usage of a command. */ -public class CommandUsageBox extends MessageBox { +public class CommandUsageBox extends StyledFragment { /** - * Create a new box. + * Create a new usage box. * - * @param description the command to describe - * @param title the title + * @param command the command to describe + * @param commandString the command that was used, such as "/we" or "/brush sphere" */ - public CommandUsageBox(Description description, String title) { - super(title); - checkNotNull(description); - attachCommandUsage(description); + public CommandUsageBox(CommandCallable command, String commandString) { + this(command, commandString, null); } /** - * Create a new box. + * Create a new usage box. * - * @param description the command to describe + * @param command the command to describe + * @param commandString the command that was used, such as "/we" or "/brush sphere" + * @param locals list of locals to use */ - public CommandUsageBox(Description description) { - super("Usage Help"); - checkNotNull(description); - attachCommandUsage(description); - } - - private void attachCommandUsage(Description description) { - if (description.getUsage() != null) { - getContents().append(new Label().append("Usage: ")); - getContents().append(description.getUsage()); + public CommandUsageBox(CommandCallable command, String commandString, @Nullable CommandLocals locals) { + checkNotNull(command); + checkNotNull(commandString); + if (command instanceof Dispatcher) { + attachDispatcherUsage((Dispatcher) command, commandString, locals); } else { - getContents().append(new Subtle().append("Usage information is not available.")); + attachCommandUsage(command.getDescription(), commandString); + } + } + + private void attachDispatcherUsage(Dispatcher dispatcher, String commandString, @Nullable CommandLocals locals) { + CommandListBox box = new CommandListBox("Subcommands"); + String prefix = !commandString.isEmpty() ? commandString + " " : ""; + for (CommandMapping mapping : dispatcher.getCommands()) { + if (locals == null || mapping.getCallable().testPermission(locals)) { + box.appendCommand(prefix + mapping.getPrimaryAlias(), mapping.getDescription().getShortDescription()); + } } - getContents().newLine(); + append(box); + } + + private void attachCommandUsage(Description description, String commandString) { + MessageBox box = new MessageBox("Help for " + commandString); + StyledFragment contents = box.getContents(); + + if (description.getUsage() != null) { + contents.append(new Label().append("Usage: ")); + contents.append(description.getUsage()); + } else { + contents.append(new Subtle().append("Usage information is not available.")); + } + + contents.newLine(); if (description.getHelp() != null) { - getContents().createFragment(Style.YELLOW_DARK).append(description.getHelp()); + contents.createFragment(Style.YELLOW_DARK).append(description.getHelp()); } else if (description.getShortDescription() != null) { - getContents().createFragment(Style.YELLOW_DARK).append(description.getShortDescription()); + contents.append(description.getShortDescription()); } else { - getContents().append(new Subtle().append("No further help is available.")); + contents.append(new Subtle().append("No further help is available.")); } + + append(box); } }