Adds more tab-completion help methods
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
This commit is contained in:
@@ -1,10 +1,16 @@
|
|||||||
package net.knarcraft.knarlib.util;
|
package net.knarcraft.knarlib.util;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.permissions.Permission;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.StringJoiner;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,6 +19,9 @@ import java.util.function.BiFunction;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class TabCompletionHelper {
|
public final class TabCompletionHelper {
|
||||||
|
|
||||||
|
private static List<String> plugins;
|
||||||
|
private static Map<String, List<String>> permissions;
|
||||||
|
|
||||||
private TabCompletionHelper() {
|
private TabCompletionHelper() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,45 +36,40 @@ public final class TabCompletionHelper {
|
|||||||
* @param filterer <p>The filterer (filterMatching) to use to filter by typed text</p>
|
* @param filterer <p>The filterer (filterMatching) to use to filter by typed text</p>
|
||||||
* @return <p>Available tab-completions</p>
|
* @return <p>Available tab-completions</p>
|
||||||
*/
|
*/
|
||||||
public static @NotNull List<String> getStringList(@NotNull List<String> values, @NotNull String typedText,
|
@NotNull
|
||||||
@Nullable BiFunction<List<String>, String, List<String>> filterer) {
|
public static List<String> getStringList(@NotNull List<String> values, @NotNull String typedText,
|
||||||
|
@Nullable BiFunction<List<String>, String, List<String>> filterer) {
|
||||||
|
// If the typed text is blank, only filter, if necessary
|
||||||
if (typedText.isBlank() || !typedText.contains(",")) {
|
if (typedText.isBlank() || !typedText.contains(",")) {
|
||||||
if (filterer != null) {
|
if (filterer != null) {
|
||||||
return filterer.apply(values, typedText);
|
return filterer.apply(values, typedText);
|
||||||
} else {
|
} else {
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// The argument base is everything the player has typed, until the last comma
|
|
||||||
List<String> arguments = List.of(typedText.split(","));
|
|
||||||
String base;
|
|
||||||
List<String> baseArguments;
|
|
||||||
if (typedText.endsWith(",")) {
|
|
||||||
baseArguments = arguments;
|
|
||||||
} else {
|
|
||||||
baseArguments = arguments.subList(0, arguments.size() - 1);
|
|
||||||
}
|
|
||||||
base = String.join(",", baseArguments) + ",";
|
|
||||||
String lastArgument = arguments.get(arguments.size() - 1);
|
|
||||||
|
|
||||||
// Filter available values by the text after the last comma only
|
|
||||||
List<String> filtered;
|
|
||||||
if (filterer != null && !typedText.endsWith(",")) {
|
|
||||||
filtered = filterer.apply(new ArrayList<>(values), lastArgument);
|
|
||||||
} else {
|
|
||||||
filtered = values;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any already used values
|
|
||||||
List<String> unused = new ArrayList<>();
|
|
||||||
for (String string : filtered) {
|
|
||||||
if (!baseArguments.contains(string)) {
|
|
||||||
unused.add(base + string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return unused;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> items = List.of(typedText.split(","));
|
||||||
|
|
||||||
|
// Filter available values by the text after the last comma only
|
||||||
|
List<String> filtered;
|
||||||
|
if (filterer != null && !typedText.endsWith(",")) {
|
||||||
|
filtered = filterer.apply(new ArrayList<>(values), items.get(items.size() - 1));
|
||||||
|
} else {
|
||||||
|
filtered = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the prefix to all filtered values
|
||||||
|
List<String> baseItems = typedText.endsWith(",") ? items : items.subList(0, items.size() - 1);
|
||||||
|
String base = String.join(",", baseItems) + ",";
|
||||||
|
List<String> unused = new ArrayList<>();
|
||||||
|
for (String string : filtered) {
|
||||||
|
// Remove any already used values
|
||||||
|
if (!baseItems.contains(string)) {
|
||||||
|
unused.add(base + string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unused;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,7 +79,8 @@ public final class TabCompletionHelper {
|
|||||||
* @param typedText <p>The text the player has started typing</p>
|
* @param typedText <p>The text the player has started typing</p>
|
||||||
* @return <p>The given string values that contain the player's typed text</p>
|
* @return <p>The given string values that contain the player's typed text</p>
|
||||||
*/
|
*/
|
||||||
public static @NotNull List<String> filterMatchingContains(@NotNull List<String> values, @NotNull String typedText) {
|
@NotNull
|
||||||
|
public static List<String> filterMatchingContains(@NotNull List<String> values, @NotNull String typedText) {
|
||||||
List<String> configValues = new ArrayList<>();
|
List<String> configValues = new ArrayList<>();
|
||||||
for (String value : values) {
|
for (String value : values) {
|
||||||
if (value.toLowerCase().contains(typedText.toLowerCase())) {
|
if (value.toLowerCase().contains(typedText.toLowerCase())) {
|
||||||
@@ -92,7 +97,8 @@ public final class TabCompletionHelper {
|
|||||||
* @param typedText <p>The text the player has started typing</p>
|
* @param typedText <p>The text the player has started typing</p>
|
||||||
* @return <p>The given string values that start with the player's typed text</p>
|
* @return <p>The given string values that start with the player's typed text</p>
|
||||||
*/
|
*/
|
||||||
public static @NotNull List<String> filterMatchingStartsWith(@NotNull List<String> values, @NotNull String typedText) {
|
@NotNull
|
||||||
|
public static List<String> filterMatchingStartsWith(@NotNull List<String> values, @NotNull String typedText) {
|
||||||
List<String> configValues = new ArrayList<>();
|
List<String> configValues = new ArrayList<>();
|
||||||
for (String value : values) {
|
for (String value : values) {
|
||||||
if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
|
if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
|
||||||
@@ -103,14 +109,181 @@ public final class TabCompletionHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the "base" string from the given arguments, not including the incomplete last argument
|
* Gets a list of enum names from the given enum type
|
||||||
*
|
*
|
||||||
* @param arguments <p>The string arguments to get the base of</p>
|
* @param enumType <p>The type of enum to list</p>
|
||||||
* @return <p>The base string</p>
|
* @param <E> <p>The type of enum to list values for</p>
|
||||||
|
* @return <p>The names of the enums in the type</p>
|
||||||
*/
|
*/
|
||||||
private static @NotNull String getBase(@NotNull List<String> arguments) {
|
@NotNull
|
||||||
List<String> baseArray = arguments.subList(0, arguments.size() - 1);
|
public static <E extends Enum<E>> List<String> getEnumList(Class<E> enumType) {
|
||||||
return String.join(",", baseArray) + ",";
|
return Arrays.stream(enumType.getEnumConstants()).map((item) -> item.name().toLowerCase()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets tab-completions with only remaining text, from a list of full strings
|
||||||
|
*
|
||||||
|
* <p>This is specifically useful when tab-completing for values that contain spaces. This allows tab-completions to
|
||||||
|
* only list remaining text missing for tab-completing each value, instead of listing the entire strings.</p>
|
||||||
|
*
|
||||||
|
* @param arguments <p>The arguments given by the user</p>
|
||||||
|
* @param filtered <p>Tab-completions filtered by user input</p>
|
||||||
|
* @param offset <p>The offset to use, if other arguments are preceding the relevant arguments</p>
|
||||||
|
* @param prefix <p>The prefix to use before each tab-completion</p>
|
||||||
|
* @return <p>The cleaned tab-completions</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static List<String> getCleanedTabCompletions(@NotNull String[] arguments, @NotNull List<String> filtered,
|
||||||
|
int offset, @NotNull String prefix) {
|
||||||
|
List<String> cleaned = new ArrayList<>();
|
||||||
|
int offsetCount = arguments.length - offset;
|
||||||
|
for (String name : filtered) {
|
||||||
|
int startIndex = arguments.length == 0 ? 0 : (offsetCount > 1 ? offsetCount - 1 : 0);
|
||||||
|
String[] parts = name.split(" ", -1);
|
||||||
|
// Skip non-matching entries
|
||||||
|
if ((offsetCount > 1 && !parts[offsetCount - 2].equalsIgnoreCase(arguments[offsetCount - 2 + offset])) ||
|
||||||
|
startIndex >= parts.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIndex == 0) {
|
||||||
|
cleaned.add(prefix + name);
|
||||||
|
} else {
|
||||||
|
// Skip the required amount of items
|
||||||
|
StringBuilder builder = new StringBuilder(prefix).append(parts[startIndex]);
|
||||||
|
for (int i = startIndex + 1; i < parts.length; i++) {
|
||||||
|
builder.append(" ").append(parts[i]);
|
||||||
|
}
|
||||||
|
cleaned.add(builder.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges all arguments to a string with spaces
|
||||||
|
*
|
||||||
|
* @param arguments <p>The arguments to merge</p>
|
||||||
|
* @param stripLastN <p>How many of the last arguments to ignore</p>
|
||||||
|
* @return <p>The merged arguments</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static String mergeArguments(@NotNull String[] arguments, int stripLastN) {
|
||||||
|
return mergeArguments(0, arguments, stripLastN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges all arguments to a string with spaces
|
||||||
|
*
|
||||||
|
* @param stripFirstN <p>How many of the first arguments to ignore</p>
|
||||||
|
* @param arguments <p>The arguments to merge</p>
|
||||||
|
* @return <p>The merged arguments</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static String mergeArguments(int stripFirstN, @NotNull String[] arguments) {
|
||||||
|
return mergeArguments(stripFirstN, arguments, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges all arguments to a string with spaces
|
||||||
|
*
|
||||||
|
* @param stripFirstN <p>How many of the first arguments to ignore</p>
|
||||||
|
* @param arguments <p>The arguments to merge</p>
|
||||||
|
* @param stripLastN <p>How many of the last arguments to ignore</p>
|
||||||
|
* @return <p>The merged arguments</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static String mergeArguments(int stripFirstN, @NotNull String[] arguments, int stripLastN) {
|
||||||
|
StringBuilder builder = new StringBuilder(arguments[stripFirstN]);
|
||||||
|
for (int i = stripFirstN + 1; i < arguments.length - stripLastN; i++) {
|
||||||
|
builder.append(" ").append(arguments[i]);
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the tab complete value for the permission typed
|
||||||
|
*
|
||||||
|
* @param typedNode <p>The full permission node typed by the player</p>
|
||||||
|
* @return <p>All known valid auto-complete options</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private static List<String> tabCompletePermission(@NotNull String typedNode) {
|
||||||
|
if (plugins == null) {
|
||||||
|
loadAvailablePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
String permissionPrefix = parsePermissionPrefix(typedNode);
|
||||||
|
typedNode = typedNode.replaceFirst(permissionPrefix, typedNode);
|
||||||
|
if (!typedNode.contains(".")) {
|
||||||
|
return TabCompletionHelper.filterMatchingStartsWith(plugins, typedNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> matchingPermissions = permissions.get(typedNode.substring(0, typedNode.lastIndexOf(".")));
|
||||||
|
if (matchingPermissions != null) {
|
||||||
|
List<String> output = TabCompletionHelper.filterMatchingStartsWith(matchingPermissions, typedNode);
|
||||||
|
return output.stream().map((item) -> permissionPrefix + item).toList();
|
||||||
|
} else {
|
||||||
|
if (!permissionPrefix.isEmpty()) {
|
||||||
|
return List.of(permissionPrefix);
|
||||||
|
} else {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses comma-separated preceding permission values as a prefix
|
||||||
|
*
|
||||||
|
* @param typedNode <p>The permission node string the user typed</p>
|
||||||
|
* @return <p>The prefix</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private static String parsePermissionPrefix(@NotNull String typedNode) {
|
||||||
|
if (!typedNode.contains(",")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder permissionListString = new StringBuilder();
|
||||||
|
String[] permissionList = typedNode.split(",");
|
||||||
|
for (int i = 0; i < permissionList.length - 1; i++) {
|
||||||
|
permissionListString.append(permissionList[i]).append(",");
|
||||||
|
}
|
||||||
|
return permissionListString.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all permissions available from bukkit plugins
|
||||||
|
*/
|
||||||
|
private static void loadAvailablePermissions() {
|
||||||
|
plugins = new ArrayList<>();
|
||||||
|
permissions = new HashMap<>();
|
||||||
|
|
||||||
|
for (Permission permission : Bukkit.getPluginManager().getPermissions()) {
|
||||||
|
loadPermission(permission.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a given permission into the proper lists and maps
|
||||||
|
*
|
||||||
|
* @param permissionName <p>The permission to load</p>
|
||||||
|
*/
|
||||||
|
private static void loadPermission(@NotNull String permissionName) {
|
||||||
|
String[] permissionParts = permissionName.split("\\.");
|
||||||
|
if (permissionParts.length == 1) {
|
||||||
|
plugins.add(permissionParts[0]);
|
||||||
|
} else if (permissionParts.length > 1) {
|
||||||
|
StringJoiner pathJoiner = new StringJoiner(".");
|
||||||
|
for (int j = 0; j < permissionParts.length - 1; j++) {
|
||||||
|
pathJoiner.add(permissionParts[j]);
|
||||||
|
}
|
||||||
|
String path = pathJoiner.toString();
|
||||||
|
List<String> permissionList = permissions.computeIfAbsent(path, k -> new ArrayList<>());
|
||||||
|
permissionList.add(permissionName);
|
||||||
|
|
||||||
|
loadPermission(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user