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;
 | 
			
		||||
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.permissions.Permission;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.StringJoiner;
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -13,6 +19,9 @@ import java.util.function.BiFunction;
 | 
			
		||||
@SuppressWarnings("unused")
 | 
			
		||||
public final class TabCompletionHelper {
 | 
			
		||||
 | 
			
		||||
    private static List<String> plugins;
 | 
			
		||||
    private static Map<String, List<String>> permissions;
 | 
			
		||||
 | 
			
		||||
    private TabCompletionHelper() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -27,45 +36,40 @@ public final class TabCompletionHelper {
 | 
			
		||||
     * @param filterer  <p>The filterer (filterMatching) to use to filter by typed text</p>
 | 
			
		||||
     * @return <p>Available tab-completions</p>
 | 
			
		||||
     */
 | 
			
		||||
    public static @NotNull List<String> getStringList(@NotNull List<String> values, @NotNull String typedText,
 | 
			
		||||
                                                      @Nullable BiFunction<List<String>, String, List<String>> filterer) {
 | 
			
		||||
    @NotNull
 | 
			
		||||
    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 (filterer != null) {
 | 
			
		||||
                return filterer.apply(values, typedText);
 | 
			
		||||
            } else {
 | 
			
		||||
                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>
 | 
			
		||||
     * @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<>();
 | 
			
		||||
        for (String value : values) {
 | 
			
		||||
            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>
 | 
			
		||||
     * @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<>();
 | 
			
		||||
        for (String value : values) {
 | 
			
		||||
            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>
 | 
			
		||||
     * @return <p>The base string</p>
 | 
			
		||||
     * @param enumType <p>The type of enum to list</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) {
 | 
			
		||||
        List<String> baseArray = arguments.subList(0, arguments.size() - 1);
 | 
			
		||||
        return String.join(",", baseArray) + ",";
 | 
			
		||||
    @NotNull
 | 
			
		||||
    public static <E extends Enum<E>> List<String> getEnumList(Class<E> enumType) {
 | 
			
		||||
        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