From ab192c9e341e7c02fe2b22a3476ae776ba719987 Mon Sep 17 00:00:00 2001 From: Aikar Date: Wed, 23 Aug 2017 23:54:59 -0400 Subject: [PATCH] Current work on command searching for help, see image http://i.imgur.com/HQ6nmvF.png @Default @Subcommand("help") @UnknownHandler public void doHelp(CommandSender sender, CommandHelp help) { help.showHelp(); } --- .../java/co/aikar/commands/BaseCommand.java | 1 + .../co/aikar/commands/CommandContexts.java | 11 +++- .../commands/CommandExecutionContext.java | 8 +++ .../java/co/aikar/commands/CommandHelp.java | 55 ++++++++++++++----- .../java/co/aikar/commands/HelpEntry.java | 22 ++++++++ .../java/co/aikar/commands/MessageKeys.java | 3 +- .../co/aikar/commands/RegisteredCommand.java | 20 +++++-- .../commands/annotation/HelpSearchTags.java | 32 +++++++++++ languages/core/acf-core_en.properties | 1 + 9 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 core/src/main/java/co/aikar/commands/annotation/HelpSearchTags.java diff --git a/core/src/main/java/co/aikar/commands/BaseCommand.java b/core/src/main/java/co/aikar/commands/BaseCommand.java index 23daff6b..ae557030 100644 --- a/core/src/main/java/co/aikar/commands/BaseCommand.java +++ b/core/src/main/java/co/aikar/commands/BaseCommand.java @@ -255,6 +255,7 @@ public abstract class BaseCommand { for (String subcmd : cmdList) { subCommands.put(subcmd, cmd); } + cmd.addSubcommands(cmdList); if (aliasNames != null) { for (String name : aliasNames) { diff --git a/core/src/main/java/co/aikar/commands/CommandContexts.java b/core/src/main/java/co/aikar/commands/CommandContexts.java index af8923ad..561e1e8f 100644 --- a/core/src/main/java/co/aikar/commands/CommandContexts.java +++ b/core/src/main/java/co/aikar/commands/CommandContexts.java @@ -148,15 +148,22 @@ public class CommandContexts { String first = c.getFirstArg(); + String last = c.getLastArg(); int page = 1; List search = null; - if (first != null && ACFUtil.isInteger(first)) { + if (last != null && ACFUtil.isInteger(last)) { + c.popLastArg(); + page = ACFUtil.parseInt(last); + if (!c.getArgs().isEmpty()) { + search = c.getArgs(); + } + } else if (first != null && ACFUtil.isInteger(first)) { c.popFirstArg(); page = ACFUtil.parseInt(first); if (!c.getArgs().isEmpty()) { search = c.getArgs(); } - } else if (first != null && !c.getArgs().isEmpty()) { + } else if (!c.getArgs().isEmpty()) { search = c.getArgs(); } CommandHelp commandHelp = manager.generateCommandHelp(); diff --git a/core/src/main/java/co/aikar/commands/CommandExecutionContext.java b/core/src/main/java/co/aikar/commands/CommandExecutionContext.java index cbda835e..b85357fc 100644 --- a/core/src/main/java/co/aikar/commands/CommandExecutionContext.java +++ b/core/src/main/java/co/aikar/commands/CommandExecutionContext.java @@ -70,10 +70,18 @@ public class CommandExecutionContext cmd = help.getRegisteredCommand(); + int searchScore = 0; for (String word : this.search) { - Pattern pattern = Pattern.compile(Pattern.quote(word)); - if (pattern.matcher(cmd.command).matches()) { - return true; - } else if (pattern.matcher(help.getDescription()).matches()) { - return true; + Pattern pattern = Pattern.compile(".*" + Pattern.quote(word) + ".*", Pattern.CASE_INSENSITIVE); + for (String subCmd : cmd.registeredSubcommands) { + Pattern subCmdPattern = Pattern.compile(".*" + Pattern.quote(subCmd) + ".*", Pattern.CASE_INSENSITIVE); + if (pattern.matcher(subCmd).matches()) { + searchScore += 3; + } else if (subCmdPattern.matcher(word).matches()) { + searchScore++; + } + } + + + if (pattern.matcher(help.getDescription()).matches()) { + searchScore += 2; + } + if (pattern.matcher(help.getParameterSyntax()).matches()) { + searchScore++; + } + if (help.getSearchTags() != null && pattern.matcher(help.getSearchTags()).matches()) { + searchScore += 2; } } - return false; + help.setSearchScore(searchScore); } public CommandManager getManager() { @@ -88,15 +106,21 @@ public class CommandHelp { } public void showHelp(CommandIssuer issuer, MessageKeyProvider format) { - getHelpEntries().forEach(e -> { - if (!matchesSearch(e)) { - return; - } + Iterator results = getHelpEntries().stream() + .filter(HelpEntry::shouldShow) + .sorted(Comparator.comparingInt(helpEntry -> helpEntry.getSearchScore() * -1)).iterator(); + if (!results.hasNext()) { + issuer.sendMessage(MessageType.ERROR, MessageKeys.NO_COMMAND_MATCHED_SEARCH, "{search}", ACFUtil.join(this.search, " ")); + results = getHelpEntries().iterator(); + } + + while (results.hasNext()) { + HelpEntry e = results.next(); String formatted = this.manager.formatMessage(issuer, MessageType.HELP, format, getFormatReplacements(e)); for (String msg : ACFPatterns.NEWLINE.split(formatted)) { issuer.sendMessageInternal(ACFUtil.rtrim(msg)); } - }); + } } /** @@ -125,5 +149,6 @@ public class CommandHelp { public void setSearch(List search) { this.search = search; + getHelpEntries().forEach(this::updateSearchScore); } } diff --git a/core/src/main/java/co/aikar/commands/HelpEntry.java b/core/src/main/java/co/aikar/commands/HelpEntry.java index 7bd41cb3..9bbce995 100644 --- a/core/src/main/java/co/aikar/commands/HelpEntry.java +++ b/core/src/main/java/co/aikar/commands/HelpEntry.java @@ -23,12 +23,18 @@ package co.aikar.commands; +import co.aikar.commands.annotation.HelpSearchTags; + public class HelpEntry { private final RegisteredCommand command; + private final String searchTags; + private int searchScore = 1; HelpEntry(RegisteredCommand command) { this.command = command; + HelpSearchTags tagsAnno = command.method.getAnnotation(HelpSearchTags.class); + this.searchTags = tagsAnno != null ? tagsAnno.value() : null; } RegisteredCommand getRegisteredCommand() { @@ -47,4 +53,20 @@ public class HelpEntry { public String getDescription(){ return this.command.helpText; } + + public void setSearchScore(int searchScore) { + this.searchScore = searchScore; + } + + public boolean shouldShow() { + return this.searchScore > 0; + } + + public int getSearchScore() { + return searchScore; + } + + public String getSearchTags() { + return searchTags; + } } diff --git a/core/src/main/java/co/aikar/commands/MessageKeys.java b/core/src/main/java/co/aikar/commands/MessageKeys.java index e79b3804..c0a2b416 100644 --- a/core/src/main/java/co/aikar/commands/MessageKeys.java +++ b/core/src/main/java/co/aikar/commands/MessageKeys.java @@ -44,7 +44,8 @@ public enum MessageKeys implements MessageKeyProvider { MUST_BE_MAX_LENGTH, NOT_ALLOWED_ON_CONSOLE, COULD_NOT_FIND_PLAYER, - HELP_FORMAT; + HELP_FORMAT, + NO_COMMAND_MATCHED_SEARCH; private final MessageKey key = MessageKey.of("acf-core." + this.name().toLowerCase()); public MessageKey getMessageKey() { diff --git a/core/src/main/java/co/aikar/commands/RegisteredCommand.java b/core/src/main/java/co/aikar/commands/RegisteredCommand.java index a3069178..0830b66f 100644 --- a/core/src/main/java/co/aikar/commands/RegisteredCommand.java +++ b/core/src/main/java/co/aikar/commands/RegisteredCommand.java @@ -27,6 +27,7 @@ import co.aikar.commands.annotation.*; import co.aikar.commands.contexts.ContextResolver; import co.aikar.commands.contexts.IssuerAwareContextResolver; import co.aikar.commands.contexts.IssuerOnlyContextResolver; +import co.aikar.commands.contexts.OptionalContextResolver; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -35,6 +36,8 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -54,6 +57,7 @@ public class RegisteredCommand registeredSubcommands = new ArrayList<>(); RegisteredCommand(BaseCommand scope, String command, Method method, String prefSubCommand) { this.scope = scope; @@ -116,13 +120,14 @@ public class RegisteredCommand resolver, Parameter parameter) { - return resolver instanceof IssuerAwareContextResolver || resolver instanceof IssuerOnlyContextResolver + return isOptionalResolver(resolver) || parameter.getAnnotation(Optional.class) != null || parameter.getAnnotation(Default.class) != null; } - private boolean isSenderAwareResolver(ContextResolver resolver) { - return resolver instanceof IssuerAwareContextResolver || resolver instanceof IssuerOnlyContextResolver; + private boolean isOptionalResolver(ContextResolver resolver) { + return resolver instanceof IssuerAwareContextResolver || resolver instanceof IssuerOnlyContextResolver + || resolver instanceof OptionalContextResolver; } void invoke(CommandIssuer sender, List args) { @@ -195,7 +200,7 @@ public class RegisteredCommand cmd) { + this.registeredSubcommands.addAll(cmd); + } } diff --git a/core/src/main/java/co/aikar/commands/annotation/HelpSearchTags.java b/core/src/main/java/co/aikar/commands/annotation/HelpSearchTags.java new file mode 100644 index 00000000..e125f6fa --- /dev/null +++ b/core/src/main/java/co/aikar/commands/annotation/HelpSearchTags.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * 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 co.aikar.commands.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface HelpSearchTags { + String value(); +} diff --git a/languages/core/acf-core_en.properties b/languages/core/acf-core_en.properties index 6cba2612..de42f61a 100644 --- a/languages/core/acf-core_en.properties +++ b/languages/core/acf-core_en.properties @@ -34,3 +34,4 @@ acf-core.must_be_max_length = Error: Must be less than {max} characters long. acf-core.not_allowed_on_console = Error: Console may not execute this command. acf-core.could_not_find_player = Error: Could not find a player by the name: {search} acf-core.help_format = {command} {parameters} {seperator} {description} +acf-core.no_command_matched_search = No command matched {search}.