From 057f5e8cfa6555c9c4cba285c532e2f834f18920 Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Mon, 6 Apr 2020 00:10:14 +0200 Subject: [PATCH] Refactor Tab Suggestions code --- .../java/fr/pandacube/util/StringUtil.java | 8 ++ .../pandacube/util/commands/Suggestions.java | 117 ++++++++++++++++++ .../pandacube/util/commands/TabProposal.java | 87 ------------- 3 files changed, 125 insertions(+), 87 deletions(-) create mode 100644 src/main/java/fr/pandacube/util/commands/Suggestions.java delete mode 100644 src/main/java/fr/pandacube/util/commands/TabProposal.java diff --git a/src/main/java/fr/pandacube/util/StringUtil.java b/src/main/java/fr/pandacube/util/StringUtil.java index 49e45e1..e917395 100644 --- a/src/main/java/fr/pandacube/util/StringUtil.java +++ b/src/main/java/fr/pandacube/util/StringUtil.java @@ -29,4 +29,12 @@ public class StringUtil { int size = strings == null ? 0 : strings.size(); return size == 0 ? "" : size == 1 ? strings.get(0) : String.join(sep1, strings.subList(0, --size)) + sepFinal + strings.get(size); } + + + + + + public static String repeat(String base, int count) { + return new String(new char[count]).replace("\0", base); + } } diff --git a/src/main/java/fr/pandacube/util/commands/Suggestions.java b/src/main/java/fr/pandacube/util/commands/Suggestions.java new file mode 100644 index 0000000..363e804 --- /dev/null +++ b/src/main/java/fr/pandacube/util/commands/Suggestions.java @@ -0,0 +1,117 @@ +package fr.pandacube.util.commands; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@FunctionalInterface +public interface Suggestions { + + + public abstract List getSuggestions(S sender, int tokenIndex, String token, String[] args); + + + + + + + public static Predicate filter(String token) { + return suggestion -> suggestion.toLowerCase().startsWith(token.toLowerCase()); + } + + /** + * Filter the provided {@link Stream} of string according to the provided token, using the filter returned by {@link #filter(String)}, + * then returns the strings collected into a {@link List}. + * + * This methods consume the provided stream, so will not be usable anymore. + */ + public static List collectFilteredStream(Stream stream, String token) { + return stream.filter(filter(token)).sorted().collect(Collectors.toList()); + } + + + + + + public static Suggestions empty() { return (s, ti, t, a) -> Collections.emptyList(); } + + + public static Suggestions fromCollection(Collection suggestions) { + return (s, ti, token, a) -> collectFilteredStream(suggestions.stream(), token); + } + + public static Suggestions fromArray(String... suggestions) { + return (s, ti, token, a) -> collectFilteredStream(Arrays.stream(suggestions), token); + } + + + public static , S> Suggestions fromEnum(Class enumClass) { + return fromEnumValues(enumClass.getEnumConstants()); + } + + public static , S> Suggestions fromEnum(Class enumClass, boolean lowerCase) { + return fromEnumValues(lowerCase, enumClass.getEnumConstants()); + } + + @SafeVarargs + public static , S> Suggestions fromEnumValues(E... enumValues) { + return fromEnumValues(false, enumValues); + } + + @SafeVarargs + public static , S> Suggestions fromEnumValues(boolean lowerCase, E... enumValues) { + return (s, ti, token, a) -> { + Stream st = Arrays.stream(enumValues).map(Enum::name); + if (lowerCase) + st = st.map(String::toLowerCase); + return collectFilteredStream(st, token); + }; + } + + + + /** + * Create a {@link Suggestions} that support greedy strings argument using the suggestion from this {@link Suggestions}. + * @param args all the arguments currently in the buffer + * @param index the index of the first argument of the greedy string argument + * @return + */ + public default Suggestions greedyString(int index) { + + return (s, ti, token, args) -> { + + if (ti < index) + return Collections.emptyList(); + + String gToken = AbstractCommand.getLastParams(args, index); + String[] splitGToken = gToken.split(" ", -1); + int currentTokenPosition = splitGToken.length - 1; + String[] prevWordsGToken = Arrays.copyOf(splitGToken, currentTokenPosition); + + String[] argsWithMergedGreedyToken = Arrays.copyOf(args, index + 1); + argsWithMergedGreedyToken[index] = gToken; + + List currentTokenProposal = new ArrayList<>(); + for (String suggestion : getSuggestions(s, index, gToken, argsWithMergedGreedyToken)) { + String[] splitSuggestion = suggestion.split(" ", -1); + if (splitSuggestion.length <= currentTokenPosition) + continue; + if (!Arrays.equals(Arrays.copyOf(splitGToken, currentTokenPosition), prevWordsGToken)) + continue; + if (splitSuggestion[currentTokenPosition].isEmpty()) + continue; + + currentTokenProposal.add(splitSuggestion[currentTokenPosition]); + } + return currentTokenProposal; + }; + } + + + +} \ No newline at end of file diff --git a/src/main/java/fr/pandacube/util/commands/TabProposal.java b/src/main/java/fr/pandacube/util/commands/TabProposal.java deleted file mode 100644 index e9f77c8..0000000 --- a/src/main/java/fr/pandacube/util/commands/TabProposal.java +++ /dev/null @@ -1,87 +0,0 @@ -package fr.pandacube.util.commands; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -@FunctionalInterface -public interface TabProposal { - - - public abstract List getProposal(String token); - - - public static Predicate filter(String token) { - return (proposal) -> proposal.toLowerCase().startsWith(token.toLowerCase()); - } - - public static List filterStream(Stream stream, String token) { - return stream.filter(filter(token)).sorted().collect(Collectors.toList()); - } - - - - - - public static TabProposal empty() { return t -> Collections.emptyList(); } - - - public static > TabProposal fromEnum(Class enumClass) { - return fromEnumValues(enumClass.getEnumConstants()); - } - - @SafeVarargs - public static > TabProposal fromEnumValues(E... enumValues) { - return fromStream(Arrays.stream(enumValues).map(Enum::name)); - } - - public static TabProposal fromCollection(Collection proposals) { - return fromStream(proposals.stream()); - } - - public static TabProposal fromIntRange(int startIncluded, int endIncluded) { - return fromStream(IntStream.rangeClosed(startIncluded, endIncluded).mapToObj(Integer::toString)); - } - - public static TabProposal fromStream(Stream proposals) { - return t -> filterStream(proposals, t); - } - - /** - * Allow tab completion to supply proposal from multi-args (arguments with space, - * generally the last argument of a command) parameters - * @param args all the arguments currently in the buffer - * @param index the index of the first argument of the multi-args parameter - * @param proposals all possible proposals for the multi-args parameter - * @return - */ - public static TabProposal withMultiArgsLastParam(String[] args, int index, Collection proposals) { - String lastParamToken = AbstractCommand.getLastParams(args, index); - String[] splittedToken = lastParamToken.split(" ", -1); - int currentTokenPosition = splittedToken.length - 1; - String[] previousTokens = Arrays.copyOf(splittedToken, currentTokenPosition); - - return token -> { - List currentTokenProposal = new ArrayList<>(); - for (String p : proposals) { - String[] splittedProposal = p.split(" ", -1); - if (splittedProposal.length <= currentTokenPosition) - continue; - if (!Arrays.equals(Arrays.copyOf(splittedToken, currentTokenPosition), previousTokens)) - continue; - if (splittedProposal[currentTokenPosition].isEmpty()) - continue; - - if (filter(token).test(splittedProposal[currentTokenPosition])) - currentTokenProposal.add(splittedProposal[currentTokenPosition]); - } - return currentTokenProposal; - }; - } -} \ No newline at end of file