/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.lanternpowered.server.command; import static org.lanternpowered.server.text.translation.TranslationHelper.t; import com.google.common.collect.Collections2; import org.lanternpowered.server.game.Lantern; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.CommandCallable; import org.spongepowered.api.command.CommandException; import org.spongepowered.api.command.CommandMapping; import org.spongepowered.api.command.CommandResult; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.command.args.ArgumentParseException; import org.spongepowered.api.command.args.CommandArgs; import org.spongepowered.api.command.args.CommandContext; import org.spongepowered.api.command.args.CommandElement; import org.spongepowered.api.command.args.GenericArguments; import org.spongepowered.api.command.source.ConsoleSource; import org.spongepowered.api.command.spec.CommandSpec; import org.spongepowered.api.plugin.PluginContainer; import org.spongepowered.api.service.pagination.PaginationList; import org.spongepowered.api.service.pagination.PaginationService; import org.spongepowered.api.text.Text; import org.spongepowered.api.text.action.TextActions; import org.spongepowered.api.text.format.TextColors; import org.spongepowered.api.text.format.TextStyles; import org.spongepowered.api.util.StartsWithPredicate; import java.lang.reflect.Field; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.TreeSet; import java.util.stream.Collectors; import javax.annotation.Nullable; public final class CommandHelp extends CommandProvider { private static final Field extendedDescriptionField; static { try { extendedDescriptionField = CommandSpec.class.getDeclaredField("extendedDescription"); extendedDescriptionField.setAccessible(true); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } public CommandHelp() { super(0, "help", "?"); } @Override public void completeSpec(PluginContainer pluginContainer, CommandSpec.Builder specBuilder) { final Comparator<CommandMapping> comparator = (o1, o2) -> o1.getPrimaryAlias().compareTo(o2.getPrimaryAlias()); specBuilder .arguments( GenericArguments.optional(new CommandElement(Text.of("command")) { @Nullable @Override protected Object parseValue(CommandSource source, CommandArgs args) throws ArgumentParseException { return args.next(); } @Override public List<String> complete(CommandSource src, CommandArgs args, CommandContext context) { final String nextArg = args.nextIfPresent().orElse(""); return Lantern.getGame().getCommandManager().getAliases().stream() .filter(new StartsWithPredicate(nextArg)) .collect(Collectors.toList()); } }) ) .description(Text.of("View a list of all commands")) .extendedDescription(Text.of( "View a list of all commands. Hover over\n" + " a command to view its description. Click\n" + " a command to insert it into your chat bar.")) .executor((src, args) -> { Optional<String> command = args.getOne("command"); if (command.isPresent()) { Optional<? extends CommandMapping> mapping = Sponge.getCommandManager().get(command.get()); if (mapping.isPresent()) { CommandCallable callable = mapping.get().getCallable(); Optional<? extends Text> desc; // Format the command spec differently, lets include the actual // command name in the usage message if (callable instanceof CommandSpec) { Text.Builder builder = Text.builder(); callable.getShortDescription(src).ifPresent(des -> builder.append(des, Text.NEW_LINE)); builder.append(t("commands.generic.usage", t("/%s %s", command.get(), callable.getUsage(src)))); Text extendedDescription; try { // TODO: Why is there no method :( extendedDescription = (Text) extendedDescriptionField.get(callable); } catch (IllegalAccessException e) { throw new RuntimeException(e); } if (extendedDescription != null) { builder.append(Text.NEW_LINE, extendedDescription); } src.sendMessage(builder.build()); } else if ((desc = callable.getHelp(src)).isPresent()) { src.sendMessage(desc.get()); } else { src.sendMessage(t("commands.generic.usage", t("/%s %s", command.get(), callable.getUsage(src)))); } return CommandResult.success(); } throw new CommandException(Text.of("No such command: ", command.get())); } Lantern.getGame().getScheduler().submitAsyncTask(() -> { TreeSet<CommandMapping> commands = new TreeSet<>(comparator); commands.addAll(Collections2.filter(Sponge.getCommandManager().getAll().values(), input -> input.getCallable().testPermission(src))); // Console sources cannot see/use the pagination boolean paginate = !(src instanceof ConsoleSource); Text title = Text.builder("Available commands:").color(TextColors.DARK_GREEN).build(); Collection<Text> lines = Collections2.transform(commands, input -> getDescription(src, input)); if (paginate) { PaginationList.Builder builder = Sponge.getGame().getServiceManager() .provide(PaginationService.class).get().builder(); builder.title(title); builder.contents(lines); builder.sendTo(src); } else { src.sendMessage(title); src.sendMessages(lines); } return null; }); return CommandResult.success(); }); } @SuppressWarnings("unchecked") private static Text getDescription(CommandSource source, CommandMapping mapping) { final Optional<Text> description = mapping.getCallable().getShortDescription(source); Text.Builder text = Text.builder("/" + mapping.getPrimaryAlias()); text.color(TextColors.GREEN); text.style(TextStyles.UNDERLINE); text.onClick(TextActions.suggestCommand("/" + mapping.getPrimaryAlias())); Optional<? extends Text> longDescription = mapping.getCallable().getHelp(source); if (longDescription.isPresent()) { text.onHover(TextActions.showText(longDescription.get())); } return Text.of(text, " ", description.orElse(mapping.getCallable().getUsage(source))); } }