From 8e25dac340a07f7a57a13bdde53b0605779ea920 Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 15 Oct 2018 22:00:05 +0100 Subject: Implement tab completion, update readme --- .../me/lucko/spark/common/command/Command.java | 49 ++++++++-- .../spark/common/command/modules/HeapModule.java | 3 - .../common/command/modules/MonitoringModule.java | 13 ++- .../common/command/modules/SamplerModule.java | 28 +++--- .../command/tabcomplete/CompletionSupplier.java | 56 ++++++++++++ .../common/command/tabcomplete/TabCompleter.java | 100 +++++++++++++++++++++ 6 files changed, 227 insertions(+), 22 deletions(-) create mode 100644 spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/CompletionSupplier.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/TabCompleter.java (limited to 'spark-common/src/main/java/me/lucko/spark/common/command') diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/Command.java b/spark-common/src/main/java/me/lucko/spark/common/command/Command.java index 70dc7e8..a28320b 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/Command.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/Command.java @@ -20,14 +20,13 @@ package me.lucko.spark.common.command; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableList; import me.lucko.spark.common.SparkPlatform; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.Set; public class Command { @@ -35,20 +34,26 @@ public class Command { return new Builder<>(); } - private final Set aliases; + private final List aliases; + private final List arguments; private final Executor executor; private final TabCompleter tabCompleter; - private Command(Set aliases, Executor executor, TabCompleter tabCompleter) { + private Command(List aliases, List arguments, Executor executor, TabCompleter tabCompleter) { this.aliases = aliases; + this.arguments = arguments; this.executor = executor; this.tabCompleter = tabCompleter; } - public Set aliases() { + public List aliases() { return this.aliases; } + public List arguments() { + return this.arguments; + } + public Executor executor() { return this.executor; } @@ -58,7 +63,8 @@ public class Command { } public static final class Builder { - private ImmutableSet.Builder aliases = ImmutableSet.builder(); + private ImmutableList.Builder aliases = ImmutableList.builder(); + private ImmutableList.Builder arguments = ImmutableList.builder(); private Executor executor = null; private TabCompleter tabCompleter = null; @@ -71,6 +77,11 @@ public class Command { return this; } + public Builder argumentUsage(String argumentName, String parameterDescription) { + this.arguments.add(new ArgumentInfo(argumentName, parameterDescription)); + return this; + } + public Builder executor(Executor executor) { this.executor = Objects.requireNonNull(executor, "executor"); return this; @@ -82,7 +93,7 @@ public class Command { } public Command build() { - Set aliases = this.aliases.build(); + List aliases = this.aliases.build(); if (aliases.isEmpty()) { throw new IllegalStateException("No aliases defined"); } @@ -92,7 +103,7 @@ public class Command { if (this.tabCompleter == null) { this.tabCompleter = TabCompleter.empty(); } - return new Command<>(aliases, this.executor, this.tabCompleter); + return new Command<>(aliases, this.arguments.build(), this.executor, this.tabCompleter); } } @@ -110,4 +121,26 @@ public class Command { List completions(SparkPlatform platform, S sender, List arguments); } + public static final class ArgumentInfo { + private final String argumentName; + private final String parameterDescription; + + public ArgumentInfo(String argumentName, String parameterDescription) { + this.argumentName = argumentName; + this.parameterDescription = parameterDescription; + } + + public String argumentName() { + return this.argumentName; + } + + public String parameterDescription() { + return this.parameterDescription; + } + + public boolean requiresParameter() { + return this.parameterDescription != null; + } + } + } diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapModule.java index e586971..8752443 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapModule.java @@ -59,9 +59,6 @@ public class HeapModule implements CommandModule { } }); }) - .tabCompleter((platform, sender, arguments) -> { - return null; - }) .build() ); } diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/MonitoringModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/MonitoringModule.java index eafc567..a6a227f 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/MonitoringModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/MonitoringModule.java @@ -23,9 +23,14 @@ package me.lucko.spark.common.command.modules; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.command.Command; import me.lucko.spark.common.command.CommandModule; +import me.lucko.spark.common.command.tabcomplete.CompletionSupplier; +import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.monitor.TickMonitor; import me.lucko.spark.sampler.TickCounter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.function.Consumer; public class MonitoringModule implements CommandModule { @@ -37,6 +42,7 @@ public class MonitoringModule implements CommandModule { public void registerCommands(Consumer> consumer) { consumer.accept(Command.builder() .aliases("monitoring") + .argumentUsage("threshold", "percentage increase") .executor((platform, sender, arguments) -> { if (this.activeTickMonitor == null) { @@ -58,7 +64,12 @@ public class MonitoringModule implements CommandModule { } }) .tabCompleter((platform, sender, arguments) -> { - return null; + List opts = new ArrayList<>(Collections.singletonList("--threshold")); + opts.removeAll(arguments); + + return TabCompleter.create() + .from(0, CompletionSupplier.startsWith(opts)) + .complete(arguments); }) .build() ); diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java index 853aa5d..2b814e3 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java @@ -23,6 +23,8 @@ package me.lucko.spark.common.command.modules; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.command.Command; import me.lucko.spark.common.command.CommandModule; +import me.lucko.spark.common.command.tabcomplete.CompletionSupplier; +import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.http.Bytebin; import me.lucko.spark.sampler.Sampler; import me.lucko.spark.sampler.SamplerBuilder; @@ -31,6 +33,9 @@ import me.lucko.spark.sampler.ThreadGrouper; import me.lucko.spark.sampler.TickCounter; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -47,6 +52,11 @@ public class SamplerModule implements CommandModule { public void registerCommands(Consumer> consumer) { consumer.accept(Command.builder() .aliases("start") + .argumentUsage("timeout", "timeout seconds") + .argumentUsage("thread", "thread name") + .argumentUsage("not-combined", null) + .argumentUsage("interval", "interval millis") + .argumentUsage("only-ticks-over", "tick length millis") .executor((platform, sender, arguments) -> { int timeoutSeconds = arguments.intFlag("timeout"); if (timeoutSeconds != -1 && timeoutSeconds <= 10) { @@ -149,7 +159,14 @@ public class SamplerModule implements CommandModule { } }) .tabCompleter((platform, sender, arguments) -> { - return null; + List opts = new ArrayList<>(Arrays.asList("--timeout", "--interval", + "--not-combined", "--only-ticks-over")); + opts.removeAll(arguments); + opts.add("--thread"); // allowed multiple times + + return TabCompleter.create() + .from(0, CompletionSupplier.startsWith(opts)) + .complete(arguments); }) .build() ); @@ -174,9 +191,6 @@ public class SamplerModule implements CommandModule { } } }) - .tabCompleter((platform, sender, arguments) -> { - return null; - }) .build() ); @@ -194,9 +208,6 @@ public class SamplerModule implements CommandModule { } } }) - .tabCompleter((platform, sender, arguments) -> { - return null; - }) .build() ); @@ -213,9 +224,6 @@ public class SamplerModule implements CommandModule { } } }) - .tabCompleter((platform, sender, arguments) -> { - return null; - }) .build() ); } diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/CompletionSupplier.java b/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/CompletionSupplier.java new file mode 100644 index 0000000..f1a6d10 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/CompletionSupplier.java @@ -0,0 +1,56 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.spark.common.command.tabcomplete; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public interface CompletionSupplier { + + CompletionSupplier EMPTY = partial -> Collections.emptyList(); + + static CompletionSupplier startsWith(Collection strings) { + if (strings.isEmpty()) { + return EMPTY; + } + return partial -> strings.stream().filter(startsWithIgnoreCasePredicate(partial)).collect(Collectors.toList()); + } + + static Predicate startsWithIgnoreCasePredicate(String prefix) { + return string -> { + if (string.length() < prefix.length()) { + return false; + } + return string.regionMatches(true, 0, prefix, 0, prefix.length()); + }; + } + + List supplyCompletions(String partial); + +} \ No newline at end of file diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/TabCompleter.java b/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/TabCompleter.java new file mode 100644 index 0000000..f8774b2 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/TabCompleter.java @@ -0,0 +1,100 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.spark.common.command.tabcomplete; + +import com.google.common.base.Preconditions; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility for computing tab completion results + */ +public class TabCompleter { + + public static TabCompleter create() { + return new TabCompleter(); + } + + private final Map suppliers = new HashMap<>(); + private int from = Integer.MAX_VALUE; + + private TabCompleter() { + + } + + /** + * Marks that the given completion supplier should be used to compute tab + * completions at the given index. + * + * @param position the position + * @param supplier the supplier + * @return this + */ + public TabCompleter at(int position, CompletionSupplier supplier) { + Preconditions.checkState(position < this.from); + this.suppliers.put(position, supplier); + return this; + } + + /** + * Marks that the given completion supplier should be used to compute tab + * completions at the given index and at all subsequent indexes infinitely. + * + * @param position the position + * @param supplier the supplier + * @return this + */ + public TabCompleter from(int position, CompletionSupplier supplier) { + Preconditions.checkState(this.from == Integer.MAX_VALUE); + this.suppliers.put(position, supplier); + this.from = position; + return this; + } + + public List complete(List args) { + int lastIndex = 0; + String partial; + + // nothing entered yet + if (args.isEmpty() || (partial = args.get((lastIndex = args.size() - 1))).trim().isEmpty()) { + return getCompletions(lastIndex, ""); + } + + // started typing something + return getCompletions(lastIndex, partial); + } + + private List getCompletions(int position, String partial) { + if (position >= this.from) { + return this.suppliers.get(this.from).supplyCompletions(partial); + } + + return this.suppliers.getOrDefault(position, CompletionSupplier.EMPTY).supplyCompletions(partial); + } + +} \ No newline at end of file -- cgit