001 package org.bukkit.command.defaults; 002 003 import java.util.ArrayList; 004 import java.util.Arrays; 005 import java.util.HashMap; 006 import java.util.List; 007 import java.util.Map; 008 import java.util.Set; 009 import java.util.TreeSet; 010 011 import org.apache.commons.lang.ArrayUtils; 012 import org.apache.commons.lang.StringUtils; 013 import org.apache.commons.lang.Validate; 014 import org.apache.commons.lang.math.NumberUtils; 015 import org.bukkit.Bukkit; 016 import org.bukkit.ChatColor; 017 import org.bukkit.command.CommandSender; 018 import org.bukkit.command.ConsoleCommandSender; 019 import org.bukkit.help.HelpMap; 020 import org.bukkit.help.HelpTopic; 021 import org.bukkit.help.HelpTopicComparator; 022 import org.bukkit.help.IndexHelpTopic; 023 import org.bukkit.util.ChatPaginator; 024 025 import com.google.common.collect.ImmutableList; 026 027 public class HelpCommand extends VanillaCommand { 028 public HelpCommand() { 029 super("help"); 030 this.description = "Shows the help menu"; 031 this.usageMessage = "/help <pageNumber>\n/help <topic>\n/help <topic> <pageNumber>"; 032 this.setAliases(Arrays.asList(new String[] { "?" })); 033 this.setPermission("bukkit.command.help"); 034 } 035 036 @Override 037 public boolean execute(CommandSender sender, String currentAlias, String[] args) { 038 if (!testPermission(sender)) return true; 039 040 String command; 041 int pageNumber; 042 int pageHeight; 043 int pageWidth; 044 045 if (args.length == 0) { 046 command = ""; 047 pageNumber = 1; 048 } else if (NumberUtils.isDigits(args[args.length - 1])) { 049 command = StringUtils.join(ArrayUtils.subarray(args, 0, args.length - 1), " "); 050 try { 051 pageNumber = NumberUtils.createInteger(args[args.length - 1]); 052 } catch (NumberFormatException exception) { 053 pageNumber = 1; 054 } 055 if (pageNumber <= 0) { 056 pageNumber = 1; 057 } 058 } else { 059 command = StringUtils.join(args, " "); 060 pageNumber = 1; 061 } 062 063 if (sender instanceof ConsoleCommandSender) { 064 pageHeight = ChatPaginator.UNBOUNDED_PAGE_HEIGHT; 065 pageWidth = ChatPaginator.UNBOUNDED_PAGE_WIDTH; 066 } else { 067 pageHeight = ChatPaginator.CLOSED_CHAT_PAGE_HEIGHT - 1; 068 pageWidth = ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH; 069 } 070 071 HelpMap helpMap = Bukkit.getServer().getHelpMap(); 072 HelpTopic topic = helpMap.getHelpTopic(command); 073 074 if (topic == null) { 075 topic = helpMap.getHelpTopic("/" + command); 076 } 077 078 if (topic == null) { 079 topic = findPossibleMatches(command); 080 } 081 082 if (topic == null || !topic.canSee(sender)) { 083 sender.sendMessage(ChatColor.RED + "No help for " + command); 084 return true; 085 } 086 087 ChatPaginator.ChatPage page = ChatPaginator.paginate(topic.getFullText(sender), pageNumber, pageWidth, pageHeight); 088 089 StringBuilder header = new StringBuilder(); 090 header.append(ChatColor.YELLOW); 091 header.append("--------- "); 092 header.append(ChatColor.WHITE); 093 header.append("Help: "); 094 header.append(topic.getName()); 095 header.append(" "); 096 if (page.getTotalPages() > 1) { 097 header.append("("); 098 header.append(page.getPageNumber()); 099 header.append("/"); 100 header.append(page.getTotalPages()); 101 header.append(") "); 102 } 103 header.append(ChatColor.YELLOW); 104 for (int i = header.length(); i < ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH; i++) { 105 header.append("-"); 106 } 107 sender.sendMessage(header.toString()); 108 109 sender.sendMessage(page.getLines()); 110 111 return true; 112 } 113 114 @Override 115 public List<String> tabComplete(CommandSender sender, String alias, String[] args) { 116 Validate.notNull(sender, "Sender cannot be null"); 117 Validate.notNull(args, "Arguments cannot be null"); 118 Validate.notNull(alias, "Alias cannot be null"); 119 120 if (args.length == 1) { 121 List<String> matchedTopics = new ArrayList<String>(); 122 String searchString = args[0]; 123 for (HelpTopic topic : Bukkit.getServer().getHelpMap().getHelpTopics()) { 124 String trimmedTopic = topic.getName().startsWith("/") ? topic.getName().substring(1) : topic.getName(); 125 126 if (trimmedTopic.startsWith(searchString)) { 127 matchedTopics.add(trimmedTopic); 128 } 129 } 130 return matchedTopics; 131 } 132 return ImmutableList.of(); 133 } 134 135 protected HelpTopic findPossibleMatches(String searchString) { 136 int maxDistance = (searchString.length() / 5) + 3; 137 Set<HelpTopic> possibleMatches = new TreeSet<HelpTopic>(HelpTopicComparator.helpTopicComparatorInstance()); 138 139 if (searchString.startsWith("/")) { 140 searchString = searchString.substring(1); 141 } 142 143 for (HelpTopic topic : Bukkit.getServer().getHelpMap().getHelpTopics()) { 144 String trimmedTopic = topic.getName().startsWith("/") ? topic.getName().substring(1) : topic.getName(); 145 146 if (trimmedTopic.length() < searchString.length()) { 147 continue; 148 } 149 150 if (Character.toLowerCase(trimmedTopic.charAt(0)) != Character.toLowerCase(searchString.charAt(0))) { 151 continue; 152 } 153 154 if (damerauLevenshteinDistance(searchString, trimmedTopic.substring(0, searchString.length())) < maxDistance) { 155 possibleMatches.add(topic); 156 } 157 } 158 159 if (possibleMatches.size() > 0) { 160 return new IndexHelpTopic("Search", null, null, possibleMatches, "Search for: " + searchString); 161 } else { 162 return null; 163 } 164 } 165 166 /** 167 * Computes the Dameraur-Levenshtein Distance between two strings. Adapted 168 * from the algorithm at <a href="http://en.wikipedia.org/wiki/Damerau?Levenshtein_distance">Wikipedia: Damerau?Levenshtein distance</a> 169 * 170 * @param s1 The first string being compared. 171 * @param s2 The second string being compared. 172 * @return The number of substitutions, deletions, insertions, and 173 * transpositions required to get from s1 to s2. 174 */ 175 protected static int damerauLevenshteinDistance(String s1, String s2) { 176 if (s1 == null && s2 == null) { 177 return 0; 178 } 179 if (s1 != null && s2 == null) { 180 return s1.length(); 181 } 182 if (s1 == null && s2 != null) { 183 return s2.length(); 184 } 185 186 int s1Len = s1.length(); 187 int s2Len = s2.length(); 188 int[][] H = new int[s1Len + 2][s2Len + 2]; 189 190 int INF = s1Len + s2Len; 191 H[0][0] = INF; 192 for (int i = 0; i <= s1Len; i++) { 193 H[i + 1][1] = i; 194 H[i + 1][0] = INF; 195 } 196 for (int j = 0; j <= s2Len; j++) { 197 H[1][j + 1] = j; 198 H[0][j + 1] = INF; 199 } 200 201 Map<Character, Integer> sd = new HashMap<Character, Integer>(); 202 for (char Letter : (s1 + s2).toCharArray()) { 203 if (!sd.containsKey(Letter)) { 204 sd.put(Letter, 0); 205 } 206 } 207 208 for (int i = 1; i <= s1Len; i++) { 209 int DB = 0; 210 for (int j = 1; j <= s2Len; j++) { 211 int i1 = sd.get(s2.charAt(j - 1)); 212 int j1 = DB; 213 214 if (s1.charAt(i - 1) == s2.charAt(j - 1)) { 215 H[i + 1][j + 1] = H[i][j]; 216 DB = j; 217 } else { 218 H[i + 1][j + 1] = Math.min(H[i][j], Math.min(H[i + 1][j], H[i][j + 1])) + 1; 219 } 220 221 H[i + 1][j + 1] = Math.min(H[i + 1][j + 1], H[i1][j1] + (i - i1 - 1) + 1 + (j - j1 - 1)); 222 } 223 sd.put(s1.charAt(i - 1), i); 224 } 225 226 return H[s1Len + 1][s2Len + 1]; 227 } 228 }