13
0
geforkt von Mirrors/Paper

Enhance console tab completions for brigadier commands

Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
Dieser Commit ist enthalten in:
Jason Penilla 2021-03-30 16:06:08 -07:00
Ursprung 85a8224261
Commit a6eda6cb37
7 geänderte Dateien mit 340 neuen und 19 gelöschten Zeilen

Datei anzeigen

@ -35,7 +35,7 @@
@Nullable
private RconThread rconThread;
public DedicatedServerSettings settings;
@@ -81,33 +92,99 @@
@@ -81,36 +92,102 @@
private DebugSampleSubscriptionTracker debugSampleSubscriptionTracker;
public ServerLinks serverLinks;
@ -143,8 +143,12 @@
+
thread.setDaemon(true);
thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER));
thread.start();
@@ -126,13 +203,33 @@
- thread.start();
+ // thread.start(); // Paper - Enhance console tab completions for brigadier commands; 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\"");
@@ -126,13 +203,34 @@
this.setPreventProxyConnections(dedicatedserverproperties.preventProxyConnections);
this.setLocalIp(dedicatedserverproperties.serverIp);
}
@ -165,6 +169,7 @@
+ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames
+ // Paper end - fix converting txt to json file
+ org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread
+ thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized
+ io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
+ com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
+ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
@ -179,16 +184,17 @@
DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode);
InetAddress inetaddress = null;
@@ -156,21 +253,31 @@
@@ -155,22 +253,32 @@
DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?");
return false;
}
+
+ // CraftBukkit start
+ // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up
+ this.server.loadPlugins();
+ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP);
+ // CraftBukkit end
+
if (!this.usesAuthentication()) {
DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
@ -216,7 +222,7 @@
this.debugSampleSubscriptionTracker = new DebugSampleSubscriptionTracker(this.getPlayerList());
this.tickTimeLogger = new RemoteSampleLogger(TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME);
long i = Util.getNanos();
@@ -178,13 +285,13 @@
@@ -178,13 +286,13 @@
SkullBlockEntity.setup(this.services, this);
GameProfileCache.setUsesAuthentication(this.usesAuthentication());
DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
@ -232,7 +238,7 @@
}
if (dedicatedserverproperties.enableQuery) {
@@ -197,7 +304,7 @@
@@ -197,7 +305,7 @@
this.rconThread = RconThread.create(this);
}
@ -241,7 +247,7 @@
Thread thread1 = new Thread(new ServerWatchdog(this));
thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(DedicatedServer.LOGGER));
@@ -215,6 +322,12 @@
@@ -215,6 +323,12 @@
}
}
@ -254,7 +260,7 @@
@Override
public boolean isSpawningMonsters() {
return this.settings.getProperties().spawnMonsters && super.isSpawningMonsters();
@@ -227,7 +340,7 @@
@@ -227,7 +341,7 @@
@Override
public void forceDifficulty() {
@ -263,7 +269,7 @@
}
@Override
@@ -286,13 +399,14 @@
@@ -286,13 +400,14 @@
}
if (this.rconThread != null) {
@ -280,7 +286,7 @@
}
@Override
@@ -302,19 +416,29 @@
@@ -302,19 +417,29 @@
}
@Override
@ -316,7 +322,7 @@
}
}
@@ -383,7 +507,7 @@
@@ -383,7 +508,7 @@
@Override
public boolean isUnderSpawnProtection(ServerLevel world, BlockPos pos, Player player) {
@ -325,7 +331,7 @@
return false;
} else if (this.getPlayerList().getOps().isEmpty()) {
return false;
@@ -453,7 +577,11 @@
@@ -453,7 +578,11 @@
public boolean enforceSecureProfile() {
DedicatedServerProperties dedicatedserverproperties = this.getProperties();
@ -338,7 +344,7 @@
}
@Override
@@ -541,16 +669,52 @@
@@ -541,16 +670,52 @@
@Override
public String getPluginNames() {
@ -395,7 +401,7 @@
}
public void storeUsingWhiteList(boolean useWhitelist) {
@@ -660,4 +824,15 @@
@@ -660,4 +825,15 @@
}
}
}

Datei anzeigen

@ -1,5 +1,8 @@
package com.destroystokyo.paper.console;
import io.papermc.paper.configuration.GlobalConfiguration;
import io.papermc.paper.console.BrigadierCompletionMatcher;
import io.papermc.paper.console.BrigadierConsoleParser;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.bukkit.craftbukkit.command.ConsoleCommandCompleter;
@ -16,11 +19,20 @@ 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));
}
if (GlobalConfiguration.get().console.enableBrigadierCompletions) {
System.setProperty("org.jline.reader.support.parsedline", "true"); // to hide a warning message about the parser not supporting
builder.parser(new BrigadierConsoleParser(this.server));
builder.completionMatcher(new BrigadierCompletionMatcher());
}
return super.buildReader(builder);
}
@Override

Datei anzeigen

@ -0,0 +1,119 @@
package io.papermc.paper.console;
import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent;
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.kyori.adventure.text.Component;
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.checkerframework.checker.nullness.qual.Nullable;
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, new ParseContext(line.line(), 0));
return;
}
final CommandDispatcher<CommandSourceStack> dispatcher = this.server.getCommands().getDispatcher();
final ParseResults<CommandSourceStack> results = dispatcher.parse(new StringReader(line.line()), this.commandSourceStack.get());
this.addCandidates(
candidates,
dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(),
existing,
new ParseContext(line.line(), results.getContext().findSuggestionContext(line.cursor()).startPos)
);
}
private void addCandidates(
final @NonNull List<Candidate> candidates,
final @NonNull List<Suggestion> brigSuggestions,
final @NonNull List<Completion> existing,
final @NonNull ParseContext context
) {
brigSuggestions.forEach(it -> {
if (it.getText().isEmpty()) return;
candidates.add(toCandidate(it, context));
});
for (final AsyncTabCompleteEvent.Completion completion : existing) {
if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) {
continue;
}
candidates.add(toCandidate(completion));
}
}
private static Candidate toCandidate(final Suggestion suggestion, final @NonNull ParseContext context) {
Component tooltip = null;
if (suggestion.getTooltip() != null) {
tooltip = PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip()));
}
return toCandidate(context.line.substring(context.suggestionStart, suggestion.getRange().getStart()) + suggestion.getText(), tooltip);
}
private static @NonNull Candidate toCandidate(final @NonNull Completion completion) {
return toCandidate(completion.suggestion(), completion.tooltip());
}
private static @NonNull Candidate toCandidate(final @NonNull String suggestionText, final @Nullable Component tooltip) {
final String suggestionTooltip = PaperAdventure.ANSI_SERIALIZER.serializeOr(tooltip, null);
//noinspection SpellCheckingInspection
return new PaperCandidate(
suggestionText,
suggestionText,
null,
suggestionTooltip,
null,
null,
/*
in an ideal world, this would sometimes be true if the suggestion represented the final possible value for a word.
Like for `/execute alig`, pressing enter on align would add a trailing space if this value was true. But not all
suggestions should add spaces after, like `/execute as @`, accepting any suggestion here would be valid, but its also
valid to have a `[` following the selector
*/
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())));
}
private record ParseContext(String line, int suggestionStart) {
}
public static final class PaperCandidate extends Candidate {
public PaperCandidate(final String value, final String display, final String group, final String descr, final String suffix, final String key, final boolean complete) {
super(value, display, group, descr, suffix, key, complete);
}
}
}

Datei anzeigen

@ -0,0 +1,67 @@
package io.papermc.paper.console;
import com.google.common.base.Suppliers;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.StringReader;
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(new StringReader(buffer), this.commandSourceStack.get());
int pos = 0;
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) {}
}

Datei anzeigen

@ -0,0 +1,27 @@
package io.papermc.paper.console;
import com.google.common.collect.Iterables;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jline.reader.Candidate;
import org.jline.reader.CompletingParsedLine;
import org.jline.reader.LineReader;
import org.jline.reader.impl.CompletionMatcherImpl;
public class BrigadierCompletionMatcher extends CompletionMatcherImpl {
@Override
protected void defaultMatchers(final Map<LineReader.Option, Boolean> options, final boolean prefix, final CompletingParsedLine line, final boolean caseInsensitive, final int errors, final String originalGroupName) {
super.defaultMatchers(options, prefix, line, caseInsensitive, errors, originalGroupName);
this.matchers.addFirst(m -> {
final Map<String, List<Candidate>> candidates = new HashMap<>();
for (final Map.Entry<String, List<Candidate>> entry : m.entrySet()) {
if (Iterables.all(entry.getValue(), BrigadierCommandCompleter.PaperCandidate.class::isInstance)) {
candidates.put(entry.getKey(), entry.getValue());
}
}
return candidates;
});
}
}

Datei anzeigen

@ -0,0 +1,79 @@
package io.papermc.paper.console;
import com.mojang.brigadier.ImmutableStringReader;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedCommandNode;
import com.mojang.brigadier.context.StringRange;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.dedicated.DedicatedServer;
import org.jline.reader.ParsedLine;
import org.jline.reader.Parser;
import org.jline.reader.SyntaxError;
public class BrigadierConsoleParser implements Parser {
private final DedicatedServer server;
public BrigadierConsoleParser(DedicatedServer server) {
this.server = server;
}
@Override
public ParsedLine parse(final String line, final int cursor, final ParseContext context) throws SyntaxError {
final ParseResults<CommandSourceStack> results = this.server.getCommands().getDispatcher().parse(new StringReader(line), this.server.createCommandSourceStack());
final ImmutableStringReader reader = results.getReader();
final List<String> words = new ArrayList<>();
CommandContextBuilder<CommandSourceStack> currentContext = results.getContext();
int currentWordIdx = -1;
int wordIdx = -1;
int inWordCursor = -1;
if (currentContext.getRange().getLength() > 0) {
do {
for (final ParsedCommandNode<CommandSourceStack> node : currentContext.getNodes()) {
final StringRange nodeRange = node.getRange();
String current = nodeRange.get(reader);
words.add(current);
currentWordIdx++;
if (wordIdx == -1 && nodeRange.getStart() <= cursor && nodeRange.getEnd() >= cursor) {
// if cursor is in the middle of a parsed word/node
wordIdx = currentWordIdx;
inWordCursor = cursor - nodeRange.getStart();
}
}
currentContext = currentContext.getChild();
} while (currentContext != null);
}
final String leftovers = reader.getRemaining();
if (!leftovers.isEmpty() && leftovers.isBlank()) {
// if brig didn't consume the whole line, and everything else is blank, add a new empty word
currentWordIdx++;
words.add("");
if (wordIdx == -1) {
wordIdx = currentWordIdx;
inWordCursor = 0;
}
} else if (!leftovers.isEmpty()) {
// if there are unparsed leftovers, add a new word with the remaining input
currentWordIdx++;
words.add(leftovers);
if (wordIdx == -1) {
wordIdx = currentWordIdx;
inWordCursor = cursor - reader.getCursor();
}
}
if (wordIdx == -1) {
currentWordIdx++;
words.add("");
wordIdx = currentWordIdx;
inWordCursor = 0;
}
return new BrigadierParsedLine(words.get(wordIdx), inWordCursor, wordIdx, words, line, cursor);
}
record BrigadierParsedLine(String word, int wordCursor, int wordIndex, List<String> words, String line, int cursor) implements ParsedLine {
}
}

Datei anzeigen

@ -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 - Enhance console tab completions for brigadier commands
public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer
this.server = server;
this.brigadierCompleter = new io.papermc.paper.console.BrigadierCommandCompleter(this.server); // Paper - Enhance console tab completions for brigadier commands
}
// 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 - Enhance console tab completions for brigadier commands
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
}