
CommandMap contains a method that will auto-complete commands appropriately. Before the first space, it searches for commands of which the sender has permission. After the first space, it delegates to the individual command. Vanilla commands contain implementations to mimic vanilla implementation. Exception would be give, that allows for name matching; a feature we already allowed as part of the command is now supported for auto-complete as well. Plugin commands can get a tab completer set to delegate the completion for. If no tab completer is set, it can check the executor to see if it implements the tab completion interface. It will also attempt to chain calls if null gets returned from these interfaces. Plugins also implement the new TabCompleter interface, to add ease-of-use for plugin developers, similar to the onCommand() method. The default command implementation simply searches for player names. To help facilitate command completion, a utility class was added with two functions. One checks two strings, to see if the specified string starts with (ignoring case) the second. The other method uses the first to selectively copy elements from one collection to another.
232 lines
7.8 KiB
Java
232 lines
7.8 KiB
Java
package org.bukkit.command.defaults;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.HashMap;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.Set;
|
||
import java.util.TreeSet;
|
||
|
||
import org.apache.commons.lang.ArrayUtils;
|
||
import org.apache.commons.lang.StringUtils;
|
||
import org.apache.commons.lang.Validate;
|
||
import org.apache.commons.lang.math.NumberUtils;
|
||
import org.bukkit.Bukkit;
|
||
import org.bukkit.ChatColor;
|
||
import org.bukkit.command.CommandSender;
|
||
import org.bukkit.command.ConsoleCommandSender;
|
||
import org.bukkit.help.HelpMap;
|
||
import org.bukkit.help.HelpTopic;
|
||
import org.bukkit.help.HelpTopicComparator;
|
||
import org.bukkit.help.IndexHelpTopic;
|
||
import org.bukkit.util.ChatPaginator;
|
||
|
||
import com.google.common.collect.ImmutableList;
|
||
|
||
public class HelpCommand extends VanillaCommand {
|
||
public HelpCommand() {
|
||
super("help");
|
||
this.description = "Shows the help menu";
|
||
this.usageMessage = "/help <pageNumber>\n/help <topic>\n/help <topic> <pageNumber>";
|
||
this.setPermission("bukkit.command.help");
|
||
}
|
||
|
||
@Override
|
||
public boolean execute(CommandSender sender, String currentAlias, String[] args) {
|
||
if (!testPermission(sender)) return true;
|
||
|
||
String command;
|
||
int pageNumber;
|
||
int pageHeight;
|
||
int pageWidth;
|
||
|
||
if (args.length == 0) {
|
||
command = "";
|
||
pageNumber = 1;
|
||
} else if (NumberUtils.isDigits(args[args.length - 1])) {
|
||
command = StringUtils.join(ArrayUtils.subarray(args, 0, args.length - 1), " ");
|
||
try {
|
||
pageNumber = NumberUtils.createInteger(args[args.length - 1]);
|
||
} catch (NumberFormatException exception) {
|
||
pageNumber = 1;
|
||
}
|
||
if (pageNumber <= 0) {
|
||
pageNumber = 1;
|
||
}
|
||
} else {
|
||
command = StringUtils.join(args, " ");
|
||
pageNumber = 1;
|
||
}
|
||
|
||
if (sender instanceof ConsoleCommandSender) {
|
||
pageHeight = ChatPaginator.UNBOUNDED_PAGE_HEIGHT;
|
||
pageWidth = ChatPaginator.UNBOUNDED_PAGE_WIDTH;
|
||
} else {
|
||
pageHeight = ChatPaginator.CLOSED_CHAT_PAGE_HEIGHT - 1;
|
||
pageWidth = ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH;
|
||
}
|
||
|
||
HelpMap helpMap = Bukkit.getServer().getHelpMap();
|
||
HelpTopic topic = helpMap.getHelpTopic(command);
|
||
|
||
if (topic == null) {
|
||
topic = helpMap.getHelpTopic("/" + command);
|
||
}
|
||
|
||
if (topic == null) {
|
||
topic = findPossibleMatches(command);
|
||
}
|
||
|
||
if (topic == null || !topic.canSee(sender)) {
|
||
sender.sendMessage(ChatColor.RED + "No help for " + command);
|
||
return true;
|
||
}
|
||
|
||
ChatPaginator.ChatPage page = ChatPaginator.paginate(topic.getFullText(sender), pageNumber, pageWidth, pageHeight);
|
||
|
||
StringBuilder header = new StringBuilder();
|
||
header.append(ChatColor.YELLOW);
|
||
header.append("--------- ");
|
||
header.append(ChatColor.WHITE);
|
||
header.append("Help: ");
|
||
header.append(topic.getName());
|
||
header.append(" ");
|
||
if (page.getTotalPages() > 1) {
|
||
header.append("(");
|
||
header.append(page.getPageNumber());
|
||
header.append("/");
|
||
header.append(page.getTotalPages());
|
||
header.append(") ");
|
||
}
|
||
header.append(ChatColor.YELLOW);
|
||
for (int i = header.length(); i < ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH; i++) {
|
||
header.append("-");
|
||
}
|
||
sender.sendMessage(header.toString());
|
||
|
||
sender.sendMessage(page.getLines());
|
||
|
||
return true;
|
||
}
|
||
|
||
@Override
|
||
public boolean matches(String input) {
|
||
return input.equalsIgnoreCase("help") || input.equalsIgnoreCase("?");
|
||
}
|
||
|
||
@Override
|
||
public List<String> tabComplete(CommandSender sender, String alias, String[] args) {
|
||
Validate.notNull(sender, "Sender cannot be null");
|
||
Validate.notNull(args, "Arguments cannot be null");
|
||
Validate.notNull(alias, "Alias cannot be null");
|
||
|
||
if (args.length == 1) {
|
||
List<String> matchedTopics = new ArrayList<String>();
|
||
String searchString = args[0];
|
||
for (HelpTopic topic : Bukkit.getServer().getHelpMap().getHelpTopics()) {
|
||
String trimmedTopic = topic.getName().startsWith("/") ? topic.getName().substring(1) : topic.getName();
|
||
|
||
if (trimmedTopic.startsWith(searchString)) {
|
||
matchedTopics.add(trimmedTopic);
|
||
}
|
||
}
|
||
return matchedTopics;
|
||
}
|
||
return ImmutableList.of();
|
||
}
|
||
|
||
protected HelpTopic findPossibleMatches(String searchString) {
|
||
int maxDistance = (searchString.length() / 5) + 3;
|
||
Set<HelpTopic> possibleMatches = new TreeSet<HelpTopic>(HelpTopicComparator.helpTopicComparatorInstance());
|
||
|
||
if (searchString.startsWith("/")) {
|
||
searchString = searchString.substring(1);
|
||
}
|
||
|
||
for (HelpTopic topic : Bukkit.getServer().getHelpMap().getHelpTopics()) {
|
||
String trimmedTopic = topic.getName().startsWith("/") ? topic.getName().substring(1) : topic.getName();
|
||
|
||
if (trimmedTopic.length() < searchString.length()) {
|
||
continue;
|
||
}
|
||
|
||
if (Character.toLowerCase(trimmedTopic.charAt(0)) != Character.toLowerCase(searchString.charAt(0))) {
|
||
continue;
|
||
}
|
||
|
||
if (damerauLevenshteinDistance(searchString, trimmedTopic.substring(0, searchString.length())) < maxDistance) {
|
||
possibleMatches.add(topic);
|
||
}
|
||
}
|
||
|
||
if (possibleMatches.size() > 0) {
|
||
return new IndexHelpTopic("Search", null, null, possibleMatches, "Search for: " + searchString);
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Computes the Dameraur-Levenshtein Distance between two strings. Adapted
|
||
* from the algorithm at <a href="http://en.wikipedia.org/wiki/Damerau–Levenshtein_distance">Wikipedia: Damerau–Levenshtein distance</a>
|
||
*
|
||
* @param s1 The first string being compared.
|
||
* @param s2 The second string being compared.
|
||
* @return The number of substitutions, deletions, insertions, and
|
||
* transpositions required to get from s1 to s2.
|
||
*/
|
||
protected static int damerauLevenshteinDistance(String s1, String s2) {
|
||
if (s1 == null && s2 == null) {
|
||
return 0;
|
||
}
|
||
if (s1 != null && s2 == null) {
|
||
return s1.length();
|
||
}
|
||
if (s1 == null && s2 != null) {
|
||
return s2.length();
|
||
}
|
||
|
||
int s1Len = s1.length();
|
||
int s2Len = s2.length();
|
||
int[][] H = new int[s1Len + 2][s2Len + 2];
|
||
|
||
int INF = s1Len + s2Len;
|
||
H[0][0] = INF;
|
||
for (int i = 0; i <= s1Len; i++) {
|
||
H[i + 1][1] = i;
|
||
H[i + 1][0] = INF;
|
||
}
|
||
for (int j = 0; j <= s2Len; j++) {
|
||
H[1][j + 1] = j;
|
||
H[0][j + 1] = INF;
|
||
}
|
||
|
||
Map<Character, Integer> sd = new HashMap<Character, Integer>();
|
||
for (char Letter : (s1 + s2).toCharArray()) {
|
||
if (!sd.containsKey(Letter)) {
|
||
sd.put(Letter, 0);
|
||
}
|
||
}
|
||
|
||
for (int i = 1; i <= s1Len; i++) {
|
||
int DB = 0;
|
||
for (int j = 1; j <= s2Len; j++) {
|
||
int i1 = sd.get(s2.charAt(j - 1));
|
||
int j1 = DB;
|
||
|
||
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
|
||
H[i + 1][j + 1] = H[i][j];
|
||
DB = j;
|
||
} else {
|
||
H[i + 1][j + 1] = Math.min(H[i][j], Math.min(H[i + 1][j], H[i][j + 1])) + 1;
|
||
}
|
||
|
||
H[i + 1][j + 1] = Math.min(H[i + 1][j + 1], H[i1][j1] + (i - i1 - 1) + 1 + (j - j1 - 1));
|
||
}
|
||
sd.put(s1.charAt(i - 1), i);
|
||
}
|
||
|
||
return H[s1Len + 1][s2Len + 1];
|
||
}
|
||
}
|