Paper/patches/server/0592-Enhance-console-tab-completions-for-brigadier-comman.patch
Jake Potrebic 9147456fc9
Updated Upstream (CraftBukkit/Spigot) (#8815)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

CraftBukkit Changes:
ab8ace685 SPIGOT-7236: Bone meal doesn't increase use statistic
7dcb59b8e Avoid switch on material in previous commit

Spigot Changes:
19641c75 SPIGOT-7235: World.Spigot#strikeLightningEffect doesn't do anything
2023-01-27 12:52:04 -08:00

296 lines
14 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Tue, 30 Mar 2021 16:06:08 -0700
Subject: [PATCH] Enhance console tab completions for brigadier commands
diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
index a4070b59e261f0f1ac4beec47b11492f4724bf27..c5d5648f4ca603ef2b1df723b58f9caf4dd3c722 100644
--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
@@ -16,11 +16,15 @@ public final class PaperConsole extends SimpleTerminalConsole {
@Override
protected LineReader buildReader(LineReaderBuilder builder) {
- return super.buildReader(builder
+ builder
.appName("Paper")
.variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history"))
.completer(new ConsoleCommandCompleter(this.server))
- );
+ .option(LineReader.Option.COMPLETE_IN_WORD, true);
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierHighlighting) {
+ builder.highlighter(new io.papermc.paper.console.BrigadierCommandHighlighter(this.server));
+ }
+ return super.buildReader(builder);
}
@Override
diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java
new file mode 100644
index 0000000000000000000000000000000000000000..0627c98cae0b5ebdd71a849ae1299d7d3d581850
--- /dev/null
+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java
@@ -0,0 +1,99 @@
+package io.papermc.paper.console;
+
+import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion;
+import com.google.common.base.Suppliers;
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.ParseResults;
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.suggestion.Suggestion;
+import io.papermc.paper.adventure.PaperAdventure;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.network.chat.ComponentUtils;
+import net.minecraft.server.dedicated.DedicatedServer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jline.reader.Candidate;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+
+import static com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion;
+
+public final class BrigadierCommandCompleter {
+ private final Supplier<CommandSourceStack> commandSourceStack;
+ private final DedicatedServer server;
+
+ public BrigadierCommandCompleter(final @NonNull DedicatedServer server) {
+ this.server = server;
+ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack);
+ }
+
+ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List<Candidate> candidates, final @NonNull List<Completion> existing) {
+ //noinspection ConstantConditions
+ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet
+ return;
+ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierCompletions) {
+ this.addCandidates(candidates, Collections.emptyList(), existing);
+ return;
+ }
+ final CommandDispatcher<CommandSourceStack> dispatcher = this.server.getCommands().getDispatcher();
+ final ParseResults<CommandSourceStack> results = dispatcher.parse(prepareStringReader(line.line()), this.commandSourceStack.get());
+ this.addCandidates(
+ candidates,
+ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(),
+ existing
+ );
+ }
+
+ private void addCandidates(
+ final @NonNull List<Candidate> candidates,
+ final @NonNull List<Suggestion> brigSuggestions,
+ final @NonNull List<Completion> existing
+ ) {
+ final List<Completion> completions = new ArrayList<>();
+ brigSuggestions.forEach(it -> completions.add(toCompletion(it)));
+ for (final Completion completion : existing) {
+ if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) {
+ continue;
+ }
+ completions.add(completion);
+ }
+ for (final Completion completion : completions) {
+ if (completion.suggestion().isEmpty()) {
+ continue;
+ }
+ candidates.add(toCandidate(completion));
+ }
+ }
+
+ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) {
+ final String suggestionText = completion.suggestion();
+ final String suggestionTooltip = PaperAdventure.PLAIN.serializeOr(completion.tooltip(), null);
+ return new Candidate(
+ suggestionText,
+ suggestionText,
+ null,
+ suggestionTooltip,
+ null,
+ null,
+ false
+ );
+ }
+
+ private static @NonNull Completion toCompletion(final @NonNull Suggestion suggestion) {
+ if (suggestion.getTooltip() == null) {
+ return completion(suggestion.getText());
+ }
+ return completion(suggestion.getText(), PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip())));
+ }
+
+ static @NonNull StringReader prepareStringReader(final @NonNull String line) {
+ final StringReader stringReader = new StringReader(line);
+ if (stringReader.canRead() && stringReader.peek() == '/') {
+ stringReader.skip();
+ }
+ return stringReader;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd9d77d7c7f1a5a130a1f4c15e5b1e68ae3753e1
--- /dev/null
+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java
@@ -0,0 +1,70 @@
+package io.papermc.paper.console;
+
+import com.google.common.base.Suppliers;
+import com.mojang.brigadier.ParseResults;
+import com.mojang.brigadier.context.ParsedCommandNode;
+import com.mojang.brigadier.tree.LiteralCommandNode;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.server.dedicated.DedicatedServer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jline.reader.Highlighter;
+import org.jline.reader.LineReader;
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStringBuilder;
+import org.jline.utils.AttributedStyle;
+
+public final class BrigadierCommandHighlighter implements Highlighter {
+ private static final int[] COLORS = {AttributedStyle.CYAN, AttributedStyle.YELLOW, AttributedStyle.GREEN, AttributedStyle.MAGENTA, /* Client uses GOLD here, not BLUE, however there is no GOLD AttributedStyle. */ AttributedStyle.BLUE};
+ private final Supplier<CommandSourceStack> commandSourceStack;
+ private final DedicatedServer server;
+
+ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server) {
+ this.server = server;
+ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack);
+ }
+
+ @Override
+ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) {
+ //noinspection ConstantConditions
+ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet
+ return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
+ }
+ final AttributedStringBuilder builder = new AttributedStringBuilder();
+ final ParseResults<CommandSourceStack> results = this.server.getCommands().getDispatcher().parse(BrigadierCommandCompleter.prepareStringReader(buffer), this.commandSourceStack.get());
+ int pos = 0;
+ if (buffer.startsWith("/")) {
+ builder.append("/", AttributedStyle.DEFAULT);
+ pos = 1;
+ }
+ int component = -1;
+ for (final ParsedCommandNode<CommandSourceStack> node : results.getContext().getLastChild().getNodes()) {
+ if (node.getRange().getStart() >= buffer.length()) {
+ break;
+ }
+ final int start = node.getRange().getStart();
+ final int end = Math.min(node.getRange().getEnd(), buffer.length());
+ builder.append(buffer.substring(pos, start), AttributedStyle.DEFAULT);
+ if (node.getNode() instanceof LiteralCommandNode) {
+ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT);
+ } else {
+ if (++component >= COLORS.length) {
+ component = 0;
+ }
+ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT.foreground(COLORS[component]));
+ }
+ pos = end;
+ }
+ if (pos < buffer.length()) {
+ builder.append((buffer.substring(pos)), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
+ }
+ return builder.toAttributedString();
+ }
+
+ @Override
+ public void setErrorPattern(final Pattern errorPattern) {}
+
+ @Override
+ public void setErrorIndex(final int errorIndex) {}
+}
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index cc885fd0eb19516d3864a43c2e4ae785950c5ba6..cf3db466459490fd09d9c82238f413a0cc24ab80 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -173,7 +173,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
thread.setDaemon(true);
thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER));
- thread.start();
+ // thread.start(); // Paper - moved down
DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName());
if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) {
DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
@@ -205,6 +205,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames
// Paper end - moved up
org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash);
+ thread.start(); // Paper - start console thread after MinecraftServer.console & PaperConfig are initialized
io.papermc.paper.command.PaperCommands.registerCommands(this);
com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics();
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now
diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
index 14cd8ae69d9b25dc5edad4ff96ff4a9acb1f22cb..cd4ad8261e56365850068db1d83d6a8454026737 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
@@ -18,9 +18,11 @@ import org.bukkit.event.server.TabCompleteEvent;
public class ConsoleCommandCompleter implements Completer {
private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer
+ private final io.papermc.paper.console.BrigadierCommandCompleter brigadierCompleter; // Paper
public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer
this.server = server;
+ this.brigadierCompleter = new io.papermc.paper.console.BrigadierCommandCompleter(this.server); // Paper
}
// Paper start - Change method signature for JLine update
@@ -64,7 +66,7 @@ public class ConsoleCommandCompleter implements Completer {
}
}
- if (!completions.isEmpty()) {
+ if (false && !completions.isEmpty()) {
for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) {
if (completion.suggestion().isEmpty()) {
continue;
@@ -80,6 +82,7 @@ public class ConsoleCommandCompleter implements Completer {
));
}
}
+ this.addCompletions(reader, line, candidates, completions);
return;
}
@@ -99,10 +102,12 @@ public class ConsoleCommandCompleter implements Completer {
try {
List<String> offers = waitable.get();
if (offers == null) {
+ this.addCompletions(reader, line, candidates, Collections.emptyList()); // Paper
return; // Paper - Method returns void
}
// Paper start - JLine update
+ /*
for (String completion : offers) {
if (completion.isEmpty()) {
continue;
@@ -110,6 +115,8 @@ public class ConsoleCommandCompleter implements Completer {
candidates.add(new Candidate(completion));
}
+ */
+ this.addCompletions(reader, line, candidates, offers.stream().map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::completion).collect(java.util.stream.Collectors.toList()));
// Paper end
// Paper start - JLine handles cursor now
@@ -138,5 +145,9 @@ public class ConsoleCommandCompleter implements Completer {
}
return false;
}
+
+ private void addCompletions(final LineReader reader, final ParsedLine line, final List<Candidate> candidates, final List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> existing) {
+ this.brigadierCompleter.complete(reader, line, candidates, existing);
+ }
// Paper end
}