diff options
author | Luck <git@lucko.me> | 2018-10-15 22:00:05 +0100 |
---|---|---|
committer | Luck <git@lucko.me> | 2018-10-15 22:00:05 +0100 |
commit | 8e25dac340a07f7a57a13bdde53b0605779ea920 (patch) | |
tree | 3f034154fe8630f6cbf5648c9d2cb6b1426b9efe /spark-common/src/main/java | |
parent | 91775dd2ecc3f3e70dd422f68cf6d06e74db5d49 (diff) | |
download | spark-8e25dac340a07f7a57a13bdde53b0605779ea920.tar.gz spark-8e25dac340a07f7a57a13bdde53b0605779ea920.tar.bz2 spark-8e25dac340a07f7a57a13bdde53b0605779ea920.zip |
Implement tab completion, update readme
Diffstat (limited to 'spark-common/src/main/java')
7 files changed, 266 insertions, 38 deletions
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<S> { private final List<Command<S>> 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<S> { public void executeCommand(S sender, String[] args) { if (args.length == 0) { - sendInfo(sender); + sendUsage(sender); return; } @@ -93,23 +98,41 @@ public abstract class SparkPlatform<S> { } } - sendInfo(sender); + sendUsage(sender); } - private void sendInfo(S sender) { - // todo automagically generate this + public List<String> tabCompleteCommand(S sender, String[] args) { + List<String> arguments = new ArrayList<>(Arrays.asList(args)); + + if (args.length <= 1) { + List<String> 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<S> 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 <timeout seconds>]"); - sendMessage(sender, " &8[&7--thread&8 <thread name>]"); - sendMessage(sender, " &8[&7--not-combined]"); - sendMessage(sender, " &8[&7--interval&8 <interval millis>]"); - sendMessage(sender, " &8[&7--only-ticks-over&8 <tick length millis>]"); - 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 <percentage increase>]"); + for (Command<S> 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<S> { @@ -35,20 +34,26 @@ public class Command<S> { return new Builder<>(); } - private final Set<String> aliases; + private final List<String> aliases; + private final List<ArgumentInfo> arguments; private final Executor<S> executor; private final TabCompleter<S> tabCompleter; - private Command(Set<String> aliases, Executor<S> executor, TabCompleter<S> tabCompleter) { + private Command(List<String> aliases, List<ArgumentInfo> arguments, Executor<S> executor, TabCompleter<S> tabCompleter) { this.aliases = aliases; + this.arguments = arguments; this.executor = executor; this.tabCompleter = tabCompleter; } - public Set<String> aliases() { + public List<String> aliases() { return this.aliases; } + public List<ArgumentInfo> arguments() { + return this.arguments; + } + public Executor<S> executor() { return this.executor; } @@ -58,7 +63,8 @@ public class Command<S> { } public static final class Builder<S> { - private ImmutableSet.Builder<String> aliases = ImmutableSet.builder(); + private ImmutableList.Builder<String> aliases = ImmutableList.builder(); + private ImmutableList.Builder<ArgumentInfo> arguments = ImmutableList.builder(); private Executor<S> executor = null; private TabCompleter<S> tabCompleter = null; @@ -71,6 +77,11 @@ public class Command<S> { return this; } + public Builder<S> argumentUsage(String argumentName, String parameterDescription) { + this.arguments.add(new ArgumentInfo(argumentName, parameterDescription)); + return this; + } + public Builder<S> executor(Executor<S> executor) { this.executor = Objects.requireNonNull(executor, "executor"); return this; @@ -82,7 +93,7 @@ public class Command<S> { } public Command<S> build() { - Set<String> aliases = this.aliases.build(); + List<String> aliases = this.aliases.build(); if (aliases.isEmpty()) { throw new IllegalStateException("No aliases defined"); } @@ -92,7 +103,7 @@ public class Command<S> { 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<S> { List<String> completions(SparkPlatform<S> platform, S sender, List<String> 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<S> implements CommandModule<S> { } }); }) - .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<S> implements CommandModule<S> { @@ -37,6 +42,7 @@ public class MonitoringModule<S> implements CommandModule<S> { public void registerCommands(Consumer<Command<S>> consumer) { consumer.accept(Command.<S>builder() .aliases("monitoring") + .argumentUsage("threshold", "percentage increase") .executor((platform, sender, arguments) -> { if (this.activeTickMonitor == null) { @@ -58,7 +64,12 @@ public class MonitoringModule<S> implements CommandModule<S> { } }) .tabCompleter((platform, sender, arguments) -> { - return null; + List<String> 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<S> implements CommandModule<S> { public void registerCommands(Consumer<Command<S>> consumer) { consumer.accept(Command.<S>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<S> implements CommandModule<S> { } }) .tabCompleter((platform, sender, arguments) -> { - return null; + List<String> 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<S> implements CommandModule<S> { } } }) - .tabCompleter((platform, sender, arguments) -> { - return null; - }) .build() ); @@ -194,9 +208,6 @@ public class SamplerModule<S> implements CommandModule<S> { } } }) - .tabCompleter((platform, sender, arguments) -> { - return null; - }) .build() ); @@ -213,9 +224,6 @@ public class SamplerModule<S> implements CommandModule<S> { } } }) - .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) <luck@lucko.me> + * 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<String> strings) { + if (strings.isEmpty()) { + return EMPTY; + } + return partial -> strings.stream().filter(startsWithIgnoreCasePredicate(partial)).collect(Collectors.toList()); + } + + static Predicate<String> startsWithIgnoreCasePredicate(String prefix) { + return string -> { + if (string.length() < prefix.length()) { + return false; + } + return string.regionMatches(true, 0, prefix, 0, prefix.length()); + }; + } + + List<String> 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) <luck@lucko.me> + * 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<Integer, CompletionSupplier> 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<String> complete(List<String> 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<String> 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 |