338 lines
16 KiB
Diff
338 lines
16 KiB
Diff
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: jmp <jasonpenilla2@me.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/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
||
|
index c56e7fb18f9a56c8025eb70a524f028b5942da37..efc1e42d606e1c9feb1a4871c0714933ae92a1b2 100644
|
||
|
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
||
|
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
||
|
@@ -479,4 +479,11 @@ public class PaperConfig {
|
||
|
private static void fixEntityPositionDesync() {
|
||
|
fixEntityPositionDesync = getBoolean("settings.fix-entity-position-desync", fixEntityPositionDesync);
|
||
|
}
|
||
|
+
|
||
|
+ public static boolean enableBrigadierConsoleHighlighting = true;
|
||
|
+ public static boolean enableBrigadierConsoleCompletions = true;
|
||
|
+ private static void consoleSettings() {
|
||
|
+ enableBrigadierConsoleHighlighting = getBoolean("settings.console.enable-brigadier-highlighting", enableBrigadierConsoleHighlighting);
|
||
|
+ enableBrigadierConsoleCompletions = getBoolean("settings.console.enable-brigadier-completions", enableBrigadierConsoleCompletions);
|
||
|
+ }
|
||
|
}
|
||
|
diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
|
||
|
index 89eeb9d202405747409e65fcf226d95379987e29..ad87b575a0261200b280884e054a59e3ce59c41c 100644
|
||
|
--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
|
||
|
+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
|
||
|
@@ -1,5 +1,7 @@
|
||
|
package com.destroystokyo.paper.console;
|
||
|
|
||
|
+import com.destroystokyo.paper.PaperConfig;
|
||
|
+import io.papermc.paper.console.BrigadierCommandHighlighter;
|
||
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||
|
import net.minecrell.terminalconsole.SimpleTerminalConsole;
|
||
|
import org.bukkit.craftbukkit.command.ConsoleCommandCompleter;
|
||
|
@@ -16,11 +18,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 (PaperConfig.enableBrigadierConsoleHighlighting) {
|
||
|
+ builder.highlighter(new BrigadierCommandHighlighter(this.server, this.server.getServerCommandListener()));
|
||
|
+ }
|
||
|
+ 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..1b2681e2c0a7c5f7b386e861fecc3002782a09a5
|
||
|
--- /dev/null
|
||
|
+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java
|
||
|
@@ -0,0 +1,120 @@
|
||
|
+package io.papermc.paper.console;
|
||
|
+
|
||
|
+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 net.kyori.adventure.text.Component;
|
||
|
+import net.minecraft.commands.CommandListenerWrapper;
|
||
|
+import net.minecraft.network.chat.ChatComponentUtils;
|
||
|
+import net.minecraft.server.dedicated.DedicatedServer;
|
||
|
+import org.checkerframework.checker.nullness.qual.NonNull;
|
||
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
||
|
+import org.jline.reader.Candidate;
|
||
|
+import org.jline.reader.LineReader;
|
||
|
+import org.jline.reader.ParsedLine;
|
||
|
+
|
||
|
+import java.util.ArrayList;
|
||
|
+import java.util.Collections;
|
||
|
+import java.util.List;
|
||
|
+
|
||
|
+public final class BrigadierCommandCompleter {
|
||
|
+ private final CommandListenerWrapper commandSourceStack;
|
||
|
+ private final DedicatedServer server;
|
||
|
+
|
||
|
+ public BrigadierCommandCompleter(final @NonNull DedicatedServer server, final @NonNull CommandListenerWrapper commandSourceStack) {
|
||
|
+ this.server = server;
|
||
|
+ this.commandSourceStack = commandSourceStack;
|
||
|
+ }
|
||
|
+
|
||
|
+ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List<Candidate> candidates, final @NonNull List<String> stringCompletions) {
|
||
|
+ if (!com.destroystokyo.paper.PaperConfig.enableBrigadierConsoleCompletions) {
|
||
|
+ this.addCandidates(candidates, Collections.emptyList(), stringCompletions);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ final CommandDispatcher<CommandListenerWrapper> dispatcher = this.server.getCommandDispatcher().dispatcher();
|
||
|
+ final ParseResults<CommandListenerWrapper> results = dispatcher.parse(prepareStringReader(line.line()), this.commandSourceStack);
|
||
|
+ this.addCandidates(
|
||
|
+ candidates,
|
||
|
+ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(),
|
||
|
+ stringCompletions
|
||
|
+ );
|
||
|
+ }
|
||
|
+
|
||
|
+ private void addCandidates(
|
||
|
+ final @NonNull List<Candidate> candidates,
|
||
|
+ final @NonNull List<Suggestion> brigSuggestions,
|
||
|
+ final @NonNull List<String> stringSuggestions
|
||
|
+ ) {
|
||
|
+ final List<Completion> completions = new ArrayList<>();
|
||
|
+ brigSuggestions.forEach(it -> completions.add(toCompletion(it)));
|
||
|
+ for (final String string : stringSuggestions) {
|
||
|
+ if (string.isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(string))) {
|
||
|
+ continue;
|
||
|
+ }
|
||
|
+ completions.add(completion(string));
|
||
|
+ }
|
||
|
+ for (final Completion completion : completions) {
|
||
|
+ if (completion.completion().isEmpty()) {
|
||
|
+ continue;
|
||
|
+ }
|
||
|
+ candidates.add(toCandidate(completion));
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) {
|
||
|
+ final String suggestionText = completion.completion();
|
||
|
+ 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(ChatComponentUtils.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;
|
||
|
+ }
|
||
|
+
|
||
|
+ static final class Completion {
|
||
|
+ private final String completion;
|
||
|
+ private final Component tooltip;
|
||
|
+
|
||
|
+ Completion(final @NonNull String completion, final @Nullable Component tooltip) {
|
||
|
+ this.completion = completion;
|
||
|
+ this.tooltip = tooltip;
|
||
|
+ }
|
||
|
+
|
||
|
+ @NonNull String completion() {
|
||
|
+ return this.completion;
|
||
|
+ }
|
||
|
+
|
||
|
+ @Nullable Component tooltip() {
|
||
|
+ return this.tooltip;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ static @NonNull Completion completion(final @NonNull String completion) {
|
||
|
+ return new Completion(completion, null);
|
||
|
+ }
|
||
|
+
|
||
|
+ static @NonNull Completion completion(final @NonNull String completion, final @Nullable Component tooltip) {
|
||
|
+ return new Completion(completion, tooltip);
|
||
|
+ }
|
||
|
+}
|
||
|
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..d51d20a6d1c0c956cdf425503a6c1401acd9c854
|
||
|
--- /dev/null
|
||
|
+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java
|
||
|
@@ -0,0 +1,57 @@
|
||
|
+package io.papermc.paper.console;
|
||
|
+
|
||
|
+import com.mojang.brigadier.ParseResults;
|
||
|
+import com.mojang.brigadier.context.ParsedCommandNode;
|
||
|
+import com.mojang.brigadier.tree.LiteralCommandNode;
|
||
|
+import net.minecraft.commands.CommandListenerWrapper;
|
||
|
+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 CommandListenerWrapper commandSourceStack;
|
||
|
+ private final DedicatedServer server;
|
||
|
+
|
||
|
+ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server, final @NonNull CommandListenerWrapper commandSourceStack) {
|
||
|
+ this.server = server;
|
||
|
+ this.commandSourceStack = commandSourceStack;
|
||
|
+ }
|
||
|
+
|
||
|
+ @Override
|
||
|
+ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) {
|
||
|
+ final AttributedStringBuilder builder = new AttributedStringBuilder();
|
||
|
+ final ParseResults<CommandListenerWrapper> results = this.server.getCommandDispatcher().dispatcher().parse(BrigadierCommandCompleter.prepareStringReader(buffer), this.commandSourceStack);
|
||
|
+ int pos = 0;
|
||
|
+ if (buffer.startsWith("/")) {
|
||
|
+ builder.append("/", AttributedStyle.DEFAULT);
|
||
|
+ pos = 1;
|
||
|
+ }
|
||
|
+ int component = -1;
|
||
|
+ for (final ParsedCommandNode<CommandListenerWrapper> 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();
|
||
|
+ }
|
||
|
+}
|
||
|
diff --git a/src/main/java/net/minecraft/commands/CommandDispatcher.java b/src/main/java/net/minecraft/commands/CommandDispatcher.java
|
||
|
index a70e0761aeddee8fafff971b5cbd0422ab560fb5..988d1c9e9f4f29325043eb083123d12dd5f8081d 100644
|
||
|
--- a/src/main/java/net/minecraft/commands/CommandDispatcher.java
|
||
|
+++ b/src/main/java/net/minecraft/commands/CommandDispatcher.java
|
||
|
@@ -439,7 +439,7 @@ public class CommandDispatcher {
|
||
|
};
|
||
|
}
|
||
|
|
||
|
- public com.mojang.brigadier.CommandDispatcher<CommandListenerWrapper> a() {
|
||
|
+ public com.mojang.brigadier.CommandDispatcher<CommandListenerWrapper> a() { return this.dispatcher(); } public com.mojang.brigadier.CommandDispatcher<CommandListenerWrapper> dispatcher() { // Paper - OBFHELPER
|
||
|
return this.b;
|
||
|
}
|
||
|
|
||
|
diff --git a/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java b/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java
|
||
|
index b00e5d811ddfa12937f57bac4debb2fdd057d6e1..d14e4bf09bc43e78a9da07ea062ed886d27c7cc0 100644
|
||
|
--- a/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java
|
||
|
+++ b/src/main/java/net/minecraft/network/chat/ChatComponentUtils.java
|
||
|
@@ -90,7 +90,7 @@ public class ChatComponentUtils {
|
||
|
ChatComponentText chatcomponenttext = new ChatComponentText("");
|
||
|
boolean flag = true;
|
||
|
|
||
|
- for (Iterator iterator = collection.iterator(); iterator.hasNext(); flag = false) {
|
||
|
+ for (Iterator<T> iterator = collection.iterator(); iterator.hasNext(); flag = false) { // Paper - decompile fix
|
||
|
T t0 = iterator.next();
|
||
|
|
||
|
if (!flag) {
|
||
|
@@ -108,7 +108,7 @@ public class ChatComponentUtils {
|
||
|
return new ChatMessage("chat.square_brackets", new Object[]{ichatbasecomponent});
|
||
|
}
|
||
|
|
||
|
- public static IChatBaseComponent a(Message message) {
|
||
|
+ public static IChatBaseComponent a(Message message) { return fromMessage(message); } public static IChatBaseComponent fromMessage(final @org.checkerframework.checker.nullness.qual.NonNull Message message) { // Paper - OBFHELPER
|
||
|
return (IChatBaseComponent) (message instanceof IChatBaseComponent ? (IChatBaseComponent) message : new ChatComponentText(message.getString()));
|
||
|
}
|
||
|
}
|
||
|
diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
|
||
|
index c5e00bd9e2790992202aadf8eec2002fc88c78f1..bb837136285737862aa0b0f3d7fbe834bd69f741 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, this.server.getServerCommandListener()); // Paper
|
||
|
}
|
||
|
|
||
|
// Paper start - Change method signature for JLine update
|
||
|
@@ -55,9 +57,10 @@ public class ConsoleCommandCompleter implements Completer {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- if (!completions.isEmpty()) {
|
||
|
+ if (false && !completions.isEmpty()) {
|
||
|
candidates.addAll(completions.stream().map(Candidate::new).collect(java.util.stream.Collectors.toList()));
|
||
|
}
|
||
|
+ this.addCompletions(reader, line, candidates, completions);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
@@ -77,10 +80,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;
|
||
|
@@ -88,6 +93,8 @@ public class ConsoleCommandCompleter implements Completer {
|
||
|
|
||
|
candidates.add(new Candidate(completion));
|
||
|
}
|
||
|
+ */
|
||
|
+ this.addCompletions(reader, line, candidates, offers);
|
||
|
// Paper end
|
||
|
|
||
|
// Paper start - JLine handles cursor now
|
||
|
@@ -106,4 +113,10 @@ public class ConsoleCommandCompleter implements Completer {
|
||
|
Thread.currentThread().interrupt();
|
||
|
}
|
||
|
}
|
||
|
+
|
||
|
+ // Paper start
|
||
|
+ private void addCompletions(final LineReader reader, final ParsedLine line, final List<Candidate> candidates, final List<String> stringCompletions) {
|
||
|
+ this.brigadierCompleter.complete(reader, line, candidates, stringCompletions);
|
||
|
+ }
|
||
|
+ // Paper end
|
||
|
}
|