Update to Piston 0.5.2 + Doctools/Deprecation improvements (#523)

* Update to Piston 0.5.2

* [Doctools] Fix output, be verbose about deprecations

* Improve deprecation system, doctools output

(cherry picked from commit 03c0cce53e)
This commit is contained in:
Kenzie Togami 2019-10-05 05:06:18 -04:00 committed by MattBDev
parent 3431bd0644
commit 0767f56711
9 changed files with 303 additions and 64 deletions

View File

@ -27,6 +27,7 @@ import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.session.SessionKey; import com.sk89q.worldedit.session.SessionKey;
import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.auth.AuthorizationException; import com.sk89q.worldedit.util.auth.AuthorizationException;
import com.sk89q.worldedit.util.formatting.WorldEditText;
import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter; import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter;
@ -91,7 +92,7 @@ public class BukkitBlockCommandSender extends AbstractNonPlayerActor implements
@Override @Override
public void print(Component component) { public void print(Component component) {
TextAdapter.sendComponent(sender, component); TextAdapter.sendComponent(sender, WorldEditText.format(component));
} }
@Override @Override

View File

@ -25,6 +25,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.sk89q.worldedit.extension.platform.AbstractNonPlayerActor; import com.sk89q.worldedit.extension.platform.AbstractNonPlayerActor;
import com.sk89q.worldedit.session.SessionKey; import com.sk89q.worldedit.session.SessionKey;
import com.sk89q.worldedit.util.auth.AuthorizationException; import com.sk89q.worldedit.util.auth.AuthorizationException;
import com.sk89q.worldedit.util.formatting.WorldEditText;
import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter; import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter;
import java.util.UUID; import java.util.UUID;
@ -92,7 +93,7 @@ public class BukkitCommandSender extends AbstractNonPlayerActor {
@Override @Override
public void print(Component component) { public void print(Component component) {
TextAdapter.sendComponent(sender, component); TextAdapter.sendComponent(sender, WorldEditText.format(component));
} }
@Override @Override

View File

@ -38,6 +38,7 @@ import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.session.SessionKey; import com.sk89q.worldedit.session.SessionKey;
import com.sk89q.worldedit.util.HandSide; import com.sk89q.worldedit.util.HandSide;
import com.sk89q.worldedit.util.formatting.WorldEditText;
import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter; import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
@ -171,7 +172,7 @@ public class BukkitPlayer extends AbstractPlayerActor {
@Override @Override
public void print(Component component) { public void print(Component component) {
TextAdapter.sendComponent(player, component); TextAdapter.sendComponent(player, WorldEditText.format(component));
} }
@Override @Override

View File

@ -0,0 +1,69 @@
package com.sk89q.worldedit.internal.util
import com.sk89q.worldedit.util.formatting.WorldEditText
import com.sk89q.worldedit.util.formatting.text.Component
import com.sk89q.worldedit.util.formatting.text.TextComponent
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent
import com.sk89q.worldedit.util.formatting.text.event.ClickEvent
import com.sk89q.worldedit.util.formatting.text.format.TextDecoration
import org.enginehub.piston.util.TextHelper
fun reduceToRst(component: Component): String {
val formatted = WorldEditText.format(component)
return formatAsRst(formatted).toString()
}
private fun formatAsRst(component: Component, currentDeco: String? = null): CharSequence {
return when (component) {
is TextComponent -> {
val content = StringBuilder(component.content())
val deco = when {
// Actions that suggest themselves as commands are marked as code
component.isSuggestingAsCommand(content.toString()) -> "``"
component.decorations().any { it == TextDecoration.BOLD } -> "**"
component.decorations().any { it == TextDecoration.ITALIC } -> "*"
else -> null
}
component.children().joinTo(content, separator = "") {
formatAsRst(it, deco ?: currentDeco)
}
deco?.let {
require(currentDeco == null) {
"Nested decorations are hell in RST. \n" +
"Existing: $currentDeco; New: $deco\n" +
"Offender: ${TextHelper.reduceToText(component)}"
}
content.rstDeco(deco)
}
content
}
is TranslatableComponent -> {
val content = StringBuilder(component.key())
component.children().joinTo(content, separator = "") { formatAsRst(it, currentDeco) }
content
}
else -> component.children().joinToString(separator = "") { formatAsRst(it, currentDeco) }
}
}
private fun Component.isSuggestingAsCommand(text: String): Boolean {
val ce = clickEvent()
return when (ce?.action()) {
ClickEvent.Action.RUN_COMMAND,
ClickEvent.Action.SUGGEST_COMMAND ->
ce.value() == text
else -> false
}
}
private fun StringBuilder.rstDeco(deco: String): StringBuilder = apply {
ensureCapacity(length + deco.length * 2)
val insertionPoint = indexOfFirst { !it.isWhitespace() }.coerceAtLeast(0)
insert(insertionPoint, deco)
val insertionPointEnd = (indexOfLast { !it.isWhitespace() } + 1).takeUnless { it < 1 } ?: length
insert(insertionPointEnd, deco)
}

View File

@ -20,29 +20,19 @@
package com.sk89q.worldedit.command; package com.sk89q.worldedit.command;
import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.brush.BrushSettings;
import com.boydti.fawe.object.brush.InspectBrush; import com.boydti.fawe.object.brush.InspectBrush;
import com.boydti.fawe.object.brush.TargetMode; import com.google.common.collect.Collections2;
import com.boydti.fawe.object.brush.scroll.ScrollAction;
import com.boydti.fawe.object.brush.visualization.VisualMode;
import com.boydti.fawe.object.extent.ResettableExtent;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.StringMan;
import com.google.common.collect.Iterables;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseItem;
import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.command.argument.Arguments;
import com.sk89q.worldedit.command.tool.BlockDataCyler; import com.sk89q.worldedit.command.tool.BlockDataCyler;
import com.sk89q.worldedit.command.tool.BlockReplacer; import com.sk89q.worldedit.command.tool.BlockReplacer;
import com.sk89q.worldedit.command.tool.BrushTool;
import com.sk89q.worldedit.command.tool.DistanceWand; import com.sk89q.worldedit.command.tool.DistanceWand;
import com.sk89q.worldedit.command.tool.FloatingTreeRemover; import com.sk89q.worldedit.command.tool.FloatingTreeRemover;
import com.sk89q.worldedit.command.tool.FloodFillTool; import com.sk89q.worldedit.command.tool.FloodFillTool;
import com.sk89q.worldedit.command.tool.InvalidToolBindException;
import com.sk89q.worldedit.command.tool.LongRangeBuildTool; import com.sk89q.worldedit.command.tool.LongRangeBuildTool;
import com.sk89q.worldedit.command.tool.NavigationWand; import com.sk89q.worldedit.command.tool.NavigationWand;
import com.sk89q.worldedit.command.tool.QueryTool; import com.sk89q.worldedit.command.tool.QueryTool;
@ -51,25 +41,93 @@ import com.sk89q.worldedit.command.tool.TreePlanter;
import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissions;
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.platform.CommandEvent;
import com.sk89q.worldedit.extension.platform.PlatformCommandManager;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.internal.annotation.Range; import com.sk89q.worldedit.internal.command.CommandRegistrationHandler;
import com.sk89q.worldedit.internal.command.CommandArgParser; import com.sk89q.worldedit.internal.command.CommandUtil;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.util.HandSide; import com.sk89q.worldedit.util.HandSide;
import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.TreeGenerator;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.item.ItemType;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.enginehub.piston.CommandManager;
import org.enginehub.piston.CommandManagerService;
import org.enginehub.piston.CommandMetadata;
import org.enginehub.piston.CommandParameters;
import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.Command;
import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.CommandContainer;
import org.enginehub.piston.annotation.param.Arg; import org.enginehub.piston.annotation.param.Arg;
import org.enginehub.piston.annotation.param.Switch; import org.enginehub.piston.part.SubCommandPart;
import java.util.List;
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class) @CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
public class ToolCommands { public class ToolCommands {
public static void register(CommandRegistrationHandler registration,
CommandManager commandManager,
CommandManagerService commandManagerService,
WorldEdit worldEdit) {
// Collect the tool commands
CommandManager collect = commandManagerService.newCommandManager();
registration.register(
collect,
ToolCommandsRegistration.builder(),
new ToolCommands(worldEdit)
);
// Register deprecated global commands
Set<org.enginehub.piston.Command> commands = collect.getAllCommands()
.collect(Collectors.toSet());
for (org.enginehub.piston.Command command : commands) {
if (command.getAliases().contains("unbind")) {
// Don't register new /tool unbind alias
command = command.toBuilder().aliases(
Collections2.filter(command.getAliases(), alias -> !"unbind".equals(alias))
).build();
}
commandManager.register(CommandUtil.deprecate(
command, "Global tool names cause conflicts " +
"and will be removed in WorldEdit 8", ToolCommands::asNonGlobal
));
}
// Remove aliases with / in them, since it doesn't make sense for sub-commands.
Set<org.enginehub.piston.Command> nonGlobalCommands = commands.stream()
.map(command ->
command.toBuilder().aliases(
Collections2.filter(command.getAliases(), alias -> !alias.startsWith("/"))
).build()
)
.collect(Collectors.toSet());
commandManager.register("tool", command -> {
command.addPart(SubCommandPart.builder(
TranslatableComponent.of("tool"),
TextComponent.of("The tool to bind")
)
.withCommands(nonGlobalCommands)
.required()
.build());
command.description(TextComponent.of("Binds a tool to the item in your hand"));
});
}
private static String asNonGlobal(org.enginehub.piston.Command oldCommand,
CommandParameters oldParameters) {
String name = Optional.ofNullable(oldParameters.getMetadata())
.map(CommandMetadata::getCalledName)
.filter(n -> !n.startsWith("/"))
.orElseGet(oldCommand::getName);
return "/tool " + name;
}
static void setToolNone(Player player, LocalSession session, String type)
throws InvalidToolBindException {
session.setTool(player.getItemInHand(HandSide.MAIN_HAND).getType(), null);
player.print(type + " unbound from your current item.");
}
private final WorldEdit we; private final WorldEdit we;
public ToolCommands(WorldEdit we) { public ToolCommands(WorldEdit we) {

View File

@ -144,7 +144,8 @@ import java.util.stream.Stream;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.enginehub.piston.Command; import org.enginehub.piston.Command;
import org.enginehub.piston.CommandManager; import org.enginehub.piston.CommandManager;
import org.enginehub.piston.TextConfig; import org.enginehub.piston.config.ConfigHolder;
import org.enginehub.piston.config.TextConfig;
import org.enginehub.piston.converter.ArgumentConverter; import org.enginehub.piston.converter.ArgumentConverter;
import org.enginehub.piston.converter.ArgumentConverters; import org.enginehub.piston.converter.ArgumentConverters;
import org.enginehub.piston.converter.ConversionResult; import org.enginehub.piston.converter.ConversionResult;
@ -180,10 +181,6 @@ public final class PlatformCommandManager {
private static final java.util.logging.Logger COMMAND_LOG = private static final java.util.logging.Logger COMMAND_LOG =
java.util.logging.Logger.getLogger("com.sk89q.worldedit.CommandLog"); java.util.logging.Logger.getLogger("com.sk89q.worldedit.CommandLog");
static {
TextConfig.setCommandPrefix("/");
}
private final WorldEdit worldEdit; private final WorldEdit worldEdit;
private final PlatformManager platformManager; private final PlatformManager platformManager;
private final CommandManagerServiceImpl commandManagerService; private final CommandManagerServiceImpl commandManagerService;

View File

@ -19,30 +19,147 @@
package com.sk89q.worldedit.internal.command; package com.sk89q.worldedit.internal.command;
import static com.google.common.base.Preconditions.checkState;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extension.platform.PlatformCommandManager; import com.sk89q.worldedit.extension.platform.PlatformCommandManager;
import com.sk89q.worldedit.internal.util.Substring; import com.sk89q.worldedit.internal.util.Substring;
import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TextComponent;
import org.enginehub.piston.Command; import com.sk89q.worldedit.util.formatting.text.event.ClickEvent;
import org.enginehub.piston.exception.CommandException; import com.sk89q.worldedit.util.formatting.text.format.TextColor;
import org.enginehub.piston.inject.InjectedValueAccess; import com.sk89q.worldedit.util.formatting.text.format.TextDecoration;
import org.enginehub.piston.inject.Key;
import org.enginehub.piston.part.SubCommandPart;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.enginehub.piston.Command;
import static com.google.common.base.Preconditions.checkState; import org.enginehub.piston.CommandParameters;
import static java.util.stream.Collectors.toList; import org.enginehub.piston.NoInputCommandParameters;
import org.enginehub.piston.exception.CommandException;
import org.enginehub.piston.inject.InjectedValueAccess;
import org.enginehub.piston.inject.Key;
import org.enginehub.piston.part.SubCommandPart;
public class CommandUtil { public class CommandUtil {
private static final Component DEPRECATION_MARKER = TextComponent.of("This command is deprecated.");
private static Component makeDeprecatedFooter(String reason, Component newCommand) {
return TextComponent.builder()
.append(DEPRECATION_MARKER)
.append(" " + reason + ".")
.append(TextComponent.newline())
.append(TextComponent.of("Use ", TextColor.GOLD, TextDecoration.ITALIC))
.append(newCommand)
.append(TextComponent.of(" instead.", TextColor.GOLD, TextDecoration.ITALIC))
.build();
}
public interface NewCommandGenerator {
String newCommand(Command oldCommand, CommandParameters oldParameters);
}
public static Command deprecate(Command command, String reason,
NewCommandGenerator newCommandGenerator) {
Component deprecatedWarning = makeDeprecatedFooter(
reason,
newCommandSuggestion(newCommandGenerator,
NoInputCommandParameters.builder().build(),
command)
);
return command.toBuilder()
.action(parameters ->
deprecatedCommandWarning(parameters, command, reason, newCommandGenerator))
.footer(command.getFooter()
.map(existingFooter -> existingFooter
.append(TextComponent.newline()).append(deprecatedWarning))
.orElse(deprecatedWarning))
.build();
}
public static Optional<Component> footerWithoutDeprecation(Command command) {
return command.getFooter()
.filter(footer -> anyComponent(footer, Predicate.isEqual(DEPRECATION_MARKER)))
.map(footer -> Optional.of(
replaceDeprecation(footer)
))
.orElseGet(command::getFooter);
}
public static Optional<Component> deprecationWarning(Command command) {
return command.getFooter()
.map(CommandUtil::extractDeprecation)
.orElseGet(command::getFooter);
}
public static boolean isDeprecated(Command command) {
return command.getFooter()
.filter(footer -> anyComponent(footer, Predicate.isEqual(DEPRECATION_MARKER)))
.isPresent();
}
private static boolean anyComponent(Component component, Predicate<Component> test) {
return test.test(component) || component.children().stream()
.anyMatch(x -> anyComponent(x, test));
}
private static Component replaceDeprecation(Component component) {
if (component.children().stream().anyMatch(Predicate.isEqual(DEPRECATION_MARKER))) {
return TextComponent.empty();
}
return component.children(
component.children().stream()
.map(CommandUtil::replaceDeprecation)
.collect(toList())
);
}
private static Optional<Component> extractDeprecation(Component component) {
if (component.children().stream().anyMatch(Predicate.isEqual(DEPRECATION_MARKER))) {
return Optional.of(component);
}
return component.children().stream()
.map(CommandUtil::extractDeprecation)
.filter(Optional::isPresent)
.map(Optional::get)
.findAny();
}
private static int deprecatedCommandWarning(
CommandParameters parameters,
Command command,
String reason,
NewCommandGenerator generator
) throws Exception {
parameters.injectedValue(Key.of(Actor.class))
.ifPresent(actor -> {
Component suggestion = newCommandSuggestion(generator, parameters, command);
actor.print(TextComponent.of(reason + ". Please use ", TextColor.GOLD)
.append(suggestion)
.append(TextComponent.of(" instead."))
);
});
return command.getAction().run(parameters);
}
private static Component newCommandSuggestion(NewCommandGenerator generator,
CommandParameters parameters,
Command command) {
String suggestedCommand = generator.newCommand(command, parameters);
return TextComponent.of(suggestedCommand)
.decoration(TextDecoration.UNDERLINED, true)
.clickEvent(ClickEvent.suggestCommand(suggestedCommand));
}
public static Map<String, Command> getSubCommands(Command currentCommand) { public static Map<String, Command> getSubCommands(Command currentCommand) {
return currentCommand.getParts().stream() return currentCommand.getParts().stream()
.filter(p -> p instanceof SubCommandPart) .filter(p -> p instanceof SubCommandPart)

View File

@ -17,32 +17,29 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.sk89q.worldedit.bukkit; package com.sk89q.worldedit.util.formatting;
import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent; import org.enginehub.piston.config.ConfigHolder;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import org.enginehub.piston.config.TextConfig;
import org.enginehub.piston.util.TextHelper;
public class BukkitTextAdapter { public class WorldEditText {
public static final ConfigHolder CONFIG_HOLDER = ConfigHolder.create();
static {
CONFIG_HOLDER.getConfig(TextConfig.commandPrefix()).setValue("/");
}
public static Component format(Component component) {
return CONFIG_HOLDER.replace(component);
}
public static String reduceToText(Component component) { public static String reduceToText(Component component) {
StringBuilder text = new StringBuilder(); return TextHelper.reduceToText(format(component));
appendTextTo(text, component);
return text.toString();
} }
private static void appendTextTo(StringBuilder builder, Component component) { private WorldEditText() {
if (component instanceof TextComponent) {
builder.append(((TextComponent) component).content());
} else if (component instanceof TranslatableComponent) {
builder.append(((TranslatableComponent) component).key());
}
for (Component child : component.children()) {
appendTextTo(builder, child);
}
}
private BukkitTextAdapter() {
} }
} }

View File

@ -29,9 +29,9 @@ import com.sk89q.worldedit.util.formatting.text.event.HoverEvent;
import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; import com.sk89q.worldedit.util.formatting.text.format.TextDecoration;
import java.util.List; import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.enginehub.piston.ColorConfig;
import org.enginehub.piston.Command; import org.enginehub.piston.Command;
import org.enginehub.piston.CommandParameters; import org.enginehub.piston.CommandParameters;
import org.enginehub.piston.config.ColorConfig;
import org.enginehub.piston.util.HelpGenerator; import org.enginehub.piston.util.HelpGenerator;
/** /**
@ -71,15 +71,13 @@ public class CommandUsageBox extends TextComponentProducer {
.append(HelpGenerator.create(commands).getFullHelp()); .append(HelpGenerator.create(commands).getFullHelp());
if (getSubCommands(Iterables.getLast(commands)).size() > 0) { if (getSubCommands(Iterables.getLast(commands)).size() > 0) {
boxContent.append(TextComponent.newline()) boxContent.append(TextComponent.newline())
.append(TextComponent.builder("> ") .append(ColorConfig.helpText().wrap(TextComponent.builder("> ")
.color(ColorConfig.getHelpText()) .append(ColorConfig.mainText().wrap(TextComponent.builder("List Subcommands")
.append(TextComponent.builder("List Subcommands")
.color(ColorConfig.getMainText())
.decoration(TextDecoration.ITALIC, true) .decoration(TextDecoration.ITALIC, true)
.clickEvent(ClickEvent.runCommand(helpRootCommand + " -s " + commandString)) .clickEvent(ClickEvent.runCommand(helpRootCommand + " -s " + commandString))
.hoverEvent(HoverEvent.showText(TextComponent.of("List all subcommands of this command"))) .hoverEvent(HoverEvent.showText(TextComponent.of("List all subcommands of this command")))
.build()) .build()))
.build()); .build()));
} }
MessageBox box = new MessageBox("Help for " + commandString, MessageBox box = new MessageBox("Help for " + commandString,
boxContent); boxContent);