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 --- .../java/me/lucko/spark/common/SparkPlatform.java | 55 ++++++++---- .../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 +++++++++++++++++++++ 7 files changed, 266 insertions(+), 38 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') diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java index 57c205f..1de0ec9 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java @@ -27,10 +27,16 @@ import me.lucko.spark.common.command.Command; import me.lucko.spark.common.command.modules.HeapModule; import me.lucko.spark.common.command.modules.MonitoringModule; import me.lucko.spark.common.command.modules.SamplerModule; +import me.lucko.spark.common.command.tabcomplete.CompletionSupplier; +import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.sampler.ThreadDumper; import me.lucko.spark.sampler.TickCounter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; /** * Abstract command handling class used by all platforms. @@ -55,7 +61,6 @@ public abstract class SparkPlatform { private final List> commands = prepareCommands(); // abstract methods implemented by each platform - public abstract String getVersion(); public abstract String getLabel(); public abstract void sendMessage(S sender, String message); @@ -75,7 +80,7 @@ public abstract class SparkPlatform { public void executeCommand(S sender, String[] args) { if (args.length == 0) { - sendInfo(sender); + sendUsage(sender); return; } @@ -93,23 +98,41 @@ public abstract class SparkPlatform { } } - sendInfo(sender); + sendUsage(sender); } - private void sendInfo(S sender) { - // todo automagically generate this + public List tabCompleteCommand(S sender, String[] args) { + List arguments = new ArrayList<>(Arrays.asList(args)); + + if (args.length <= 1) { + List mainCommands = this.commands.stream().map(c -> c.aliases().get(0)).collect(Collectors.toList()); + return TabCompleter.create() + .at(0, CompletionSupplier.startsWith(mainCommands)) + .complete(arguments); + } + + String alias = arguments.remove(0); + for (Command command : this.commands) { + if (command.aliases().contains(alias)) { + return command.tabCompleter().completions(this, sender, arguments); + } + } + + return Collections.emptyList(); + } + + private void sendUsage(S sender) { sendPrefixedMessage(sender, "&fspark &7v" + getVersion()); - sendMessage(sender, "&b&l> &7/spark start"); - sendMessage(sender, " &8[&7--timeout&8 ]"); - sendMessage(sender, " &8[&7--thread&8 ]"); - sendMessage(sender, " &8[&7--not-combined]"); - sendMessage(sender, " &8[&7--interval&8 ]"); - sendMessage(sender, " &8[&7--only-ticks-over&8 ]"); - sendMessage(sender, "&b&l> &7/spark info"); - sendMessage(sender, "&b&l> &7/spark stop"); - sendMessage(sender, "&b&l> &7/spark cancel"); - sendMessage(sender, "&b&l> &7/spark monitoring"); - sendMessage(sender, " &8[&7--threshold&8 ]"); + for (Command command : this.commands) { + sendMessage(sender, "&b&l> &7/" + getLabel() + " " + command.aliases().get(0)); + for (Command.ArgumentInfo arg : command.arguments()) { + if (arg.requiresParameter()) { + sendMessage(sender, " &8[&7--" + arg.argumentName() + "&8 <" + arg.parameterDescription() + ">]"); + } else { + sendMessage(sender, " &8[&7--" + arg.argumentName() + "]"); + } + } + } } } 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