Allow for Component suggestion tooltips in AsyncTabCompleteEvent (#5504)

This commit is contained in:
Jason 2021-05-07 16:12:03 -07:00 committed by GitHub
parent 0aea6c2ff3
commit 29785297b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 573 additions and 55 deletions

View file

@ -0,0 +1,407 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: jmp <jasonpenilla2@me.com>
Date: Thu, 1 Apr 2021 00:34:41 -0700
Subject: [PATCH] Allow for Component suggestion tooltips in
AsyncTabCompleteEvent
diff --git a/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java b/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java
index 619ed37169c126a8c75d02699a04728bac49d10d..31c90ab0710020a2ff104b5cb8af604f3d682f31 100644
--- a/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java
+++ b/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java
@@ -24,6 +24,11 @@
package com.destroystokyo.paper.event.server;
import com.google.common.collect.ImmutableList;
+import io.papermc.paper.util.TransformingRandomAccessList;
+import net.kyori.adventure.text.Component;
+import net.kyori.examination.Examinable;
+import net.kyori.examination.ExaminableProperty;
+import net.kyori.examination.string.StringExaminer;
import org.apache.commons.lang.Validate;
import org.bukkit.Location;
import org.bukkit.command.Command;
@@ -34,6 +39,7 @@ import org.bukkit.event.HandlerList;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -48,15 +54,29 @@ public class AsyncTabCompleteEvent extends Event implements Cancellable {
private final boolean isCommand;
@Nullable
private final Location loc;
- @NotNull private List<String> completions;
+ private final List<Completion> completions = new ArrayList<>();
+ private final List<String> stringCompletions = new TransformingRandomAccessList<>(
+ this.completions,
+ Completion::suggestion,
+ Completion::completion
+ );
private boolean cancelled;
private boolean handled = false;
private boolean fireSyncHandler = true;
+ public AsyncTabCompleteEvent(@NotNull CommandSender sender, @NotNull String buffer, boolean isCommand, @Nullable Location loc) {
+ super(true);
+ this.sender = sender;
+ this.buffer = buffer;
+ this.isCommand = isCommand;
+ this.loc = loc;
+ }
+
+ @Deprecated
public AsyncTabCompleteEvent(@NotNull CommandSender sender, @NotNull List<String> completions, @NotNull String buffer, boolean isCommand, @Nullable Location loc) {
super(true);
this.sender = sender;
- this.completions = completions;
+ this.completions.addAll(fromStrings(completions));
this.buffer = buffer;
this.isCommand = isCommand;
this.loc = loc;
@@ -84,7 +104,7 @@ public class AsyncTabCompleteEvent extends Event implements Cancellable {
*/
@NotNull
public List<String> getCompletions() {
- return completions;
+ return this.stringCompletions;
}
/**
@@ -98,8 +118,42 @@ public class AsyncTabCompleteEvent extends Event implements Cancellable {
* @param completions the new completions
*/
public void setCompletions(@NotNull List<String> completions) {
+ if (completions == this.stringCompletions) {
+ return;
+ }
Validate.notNull(completions);
- this.completions = new ArrayList<>(completions);
+ this.completions.clear();
+ this.completions.addAll(fromStrings(completions));
+ }
+
+ /**
+ * The list of {@link Completion completions} which will be offered to the sender, in order.
+ * This list is mutable and reflects what will be offered.
+ * <p>
+ * If this collection is not empty after the event is fired, then
+ * the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])}
+ * or current player names will not be called.
+ *
+ * @return a list of offered completions
+ */
+ public @NotNull List<@NotNull Completion> completions() {
+ return this.completions;
+ }
+
+ /**
+ * Set the {@link Completion completions} offered, overriding any already set.
+ * If this collection is not empty after the event is fired, then
+ * the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])}
+ * or current player names will not be called.
+ * <p>
+ * The passed collection will be cloned to a new List. You must call {{@link #completions()}} to mutate from here
+ *
+ * @param newCompletions the new completions
+ */
+ public void completions(final @NotNull List<@NotNull Completion> newCompletions) {
+ Validate.notNull(newCompletions, "new completions");
+ this.completions.clear();
+ this.completions.addAll(newCompletions);
}
/**
@@ -174,4 +228,102 @@ public class AsyncTabCompleteEvent extends Event implements Cancellable {
public static HandlerList getHandlerList() {
return handlers;
}
+
+ private static @NotNull List<Completion> fromStrings(final @NotNull List<String> strings) {
+ final List<Completion> list = new ArrayList<>();
+ for (final String it : strings) {
+ list.add(new CompletionImpl(it, null));
+ }
+ return list;
+ }
+
+ /**
+ * A rich tab completion, consisting of a string suggestion, and a nullable {@link Component} tooltip.
+ */
+ public interface Completion extends Examinable {
+ /**
+ * Get the suggestion string for this {@link Completion}.
+ *
+ * @return suggestion string
+ */
+ @NotNull String suggestion();
+
+ /**
+ * Get the suggestion tooltip for this {@link Completion}.
+ *
+ * @return tooltip component
+ */
+ @Nullable Component tooltip();
+
+ @Override
+ default @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
+ return Stream.of(ExaminableProperty.of("suggestion", this.suggestion()), ExaminableProperty.of("tooltip", this.tooltip()));
+ }
+
+ /**
+ * Create a new {@link Completion} from a suggestion string.
+ *
+ * @param suggestion suggestion string
+ * @return new completion instance
+ */
+ static @NotNull Completion completion(final @NotNull String suggestion) {
+ return new CompletionImpl(suggestion, null);
+ }
+
+ /**
+ * Create a new {@link Completion} from a suggestion string and a tooltip {@link Component}.
+ *
+ * <p>If the provided component is null, the suggestion will not have a tooltip.</p>
+ *
+ * @param suggestion suggestion string
+ * @param tooltip tooltip component, or null
+ * @return new completion instance
+ */
+ static @NotNull Completion completion(final @NotNull String suggestion, final @Nullable Component tooltip) {
+ return new CompletionImpl(suggestion, tooltip);
+ }
+ }
+
+ static final class CompletionImpl implements Completion {
+ private final String suggestion;
+ private final Component tooltip;
+
+ CompletionImpl(final @NotNull String suggestion, final @Nullable Component tooltip) {
+ this.suggestion = suggestion;
+ this.tooltip = tooltip;
+ }
+
+ @Override
+ public @NotNull String suggestion() {
+ return this.suggestion;
+ }
+
+ @Override
+ public @Nullable Component tooltip() {
+ return this.tooltip;
+ }
+
+ @Override
+ public boolean equals(final @Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || this.getClass() != o.getClass()) {
+ return false;
+ }
+ final CompletionImpl that = (CompletionImpl) o;
+ return this.suggestion.equals(that.suggestion)
+ && java.util.Objects.equals(this.tooltip, that.tooltip);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(this.suggestion, this.tooltip);
+ }
+
+ @Override
+ public @NotNull String toString() {
+ return StringExaminer.simpleEscaping().examine(this);
+ }
+ }
}
diff --git a/src/main/java/io/papermc/paper/util/TransformingRandomAccessList.java b/src/main/java/io/papermc/paper/util/TransformingRandomAccessList.java
new file mode 100644
index 0000000000000000000000000000000000000000..6f560a51277ccbd46a9142cfa057d276118c1c7b
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/TransformingRandomAccessList.java
@@ -0,0 +1,169 @@
+package io.papermc.paper.util;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.AbstractList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.RandomAccess;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Modified version of the Guava class with the same name to support add operations.
+ *
+ * @param <F> backing list element type
+ * @param <T> transformed list element type
+ */
+public final class TransformingRandomAccessList<F, T> extends AbstractList<T> implements RandomAccess {
+ final List<F> fromList;
+ final Function<? super F, ? extends T> toFunction;
+ final Function<? super T, ? extends F> fromFunction;
+
+ /**
+ * Create a new {@link TransformingRandomAccessList}.
+ *
+ * @param fromList backing list
+ * @param toFunction function mapping backing list element type to transformed list element type
+ * @param fromFunction function mapping transformed list element type to backing list element type
+ */
+ public TransformingRandomAccessList(
+ final @NonNull List<F> fromList,
+ final @NonNull Function<? super F, ? extends T> toFunction,
+ final @NonNull Function<? super T, ? extends F> fromFunction
+ ) {
+ this.fromList = checkNotNull(fromList);
+ this.toFunction = checkNotNull(toFunction);
+ this.fromFunction = checkNotNull(fromFunction);
+ }
+
+ @Override
+ public void clear() {
+ this.fromList.clear();
+ }
+
+ @Override
+ public T get(int index) {
+ return this.toFunction.apply(this.fromList.get(index));
+ }
+
+ @Override
+ public @NotNull Iterator<T> iterator() {
+ return this.listIterator();
+ }
+
+ @Override
+ public @NotNull ListIterator<T> listIterator(int index) {
+ return new TransformedListIterator<F, T>(this.fromList.listIterator(index)) {
+ @Override
+ T transform(F from) {
+ return TransformingRandomAccessList.this.toFunction.apply(from);
+ }
+
+ @Override
+ F transformBack(T from) {
+ return TransformingRandomAccessList.this.fromFunction.apply(from);
+ }
+ };
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.fromList.isEmpty();
+ }
+
+ @Override
+ public boolean removeIf(Predicate<? super T> filter) {
+ checkNotNull(filter);
+ return this.fromList.removeIf(element -> filter.test(this.toFunction.apply(element)));
+ }
+
+ @Override
+ public T remove(int index) {
+ return this.toFunction.apply(this.fromList.remove(index));
+ }
+
+ @Override
+ public int size() {
+ return this.fromList.size();
+ }
+
+ @Override
+ public T set(int i, T t) {
+ return this.toFunction.apply(this.fromList.set(i, this.fromFunction.apply(t)));
+ }
+
+ @Override
+ public void add(int i, T t) {
+ this.fromList.add(i, this.fromFunction.apply(t));
+ }
+
+ static abstract class TransformedListIterator<F, T> implements ListIterator<T>, Iterator<T> {
+ final Iterator<F> backingIterator;
+
+ TransformedListIterator(ListIterator<F> backingIterator) {
+ this.backingIterator = checkNotNull((Iterator<F>) backingIterator);
+ }
+
+ private ListIterator<F> backingIterator() {
+ return cast(this.backingIterator);
+ }
+
+ static <A> ListIterator<A> cast(Iterator<A> iterator) {
+ return (ListIterator<A>) iterator;
+ }
+
+ @Override
+ public final boolean hasPrevious() {
+ return this.backingIterator().hasPrevious();
+ }
+
+ @Override
+ public final T previous() {
+ return this.transform(this.backingIterator().previous());
+ }
+
+ @Override
+ public final int nextIndex() {
+ return this.backingIterator().nextIndex();
+ }
+
+ @Override
+ public final int previousIndex() {
+ return this.backingIterator().previousIndex();
+ }
+
+ @Override
+ public void set(T element) {
+ this.backingIterator().set(this.transformBack(element));
+ }
+
+ @Override
+ public void add(T element) {
+ this.backingIterator().add(this.transformBack(element));
+ }
+
+ abstract T transform(F from);
+
+ abstract F transformBack(T to);
+
+ @Override
+ public final boolean hasNext() {
+ return this.backingIterator.hasNext();
+ }
+
+ @Override
+ public final T next() {
+ return this.transform(this.backingIterator.next());
+ }
+
+ @Override
+ public final void remove() {
+ this.backingIterator.remove();
+ }
+ }
+}
diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java
index 19271057cf24329757c9419fa6c97848e008a96c..82b2783497947f336b0dd95db61f31f8f77f446c 100644
--- a/src/test/java/org/bukkit/AnnotationTest.java
+++ b/src/test/java/org/bukkit/AnnotationTest.java
@@ -48,6 +48,8 @@ public class AnnotationTest {
// Generic functional interface
"org/bukkit/util/Consumer",
// Paper start
+ "io/papermc/paper/util/TransformingRandomAccessList",
+ "io/papermc/paper/util/TransformingRandomAccessList$TransformedListIterator",
// Timings history is broken in terms of nullability due to guavas Function defining that the param is NonNull
"co/aikar/timings/TimingHistory$2",
"co/aikar/timings/TimingHistory$2$1",

View file

@ -0,0 +1,132 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: jmp <jasonpenilla2@me.com>
Date: Thu, 1 Apr 2021 00:34:02 -0700
Subject: [PATCH] Allow for Component suggestion tooltips in
AsyncTabCompleteEvent
diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java
index 6ad075907d56a8f41ca3a7b82ff90a6d3ad9f1d4..db42f3b524adc4458c9c468b0299332e73a07a44 100644
--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java
+++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java
@@ -771,12 +771,11 @@ public class PlayerConnection implements PacketListenerPlayIn {
// Paper start - async tab completion
com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event;
- java.util.List<String> completions = new java.util.ArrayList<>();
String buffer = packetplayintabcomplete.c();
- event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getPlayer(), completions,
+ event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getPlayer(),
buffer, true, null);
event.callEvent();
- completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.getCompletions();
+ java.util.List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions();
// If the event isn't handled, we can assume that we have no completions, and so we'll ask the server
if (!event.isHandled()) {
if (!event.isCancelled()) {
@@ -795,10 +794,16 @@ public class PlayerConnection implements PacketListenerPlayIn {
});
}
} else if (!completions.isEmpty()) {
- com.mojang.brigadier.suggestion.SuggestionsBuilder builder = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packetplayintabcomplete.c(), stringreader.getTotalLength());
+ com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packetplayintabcomplete.c(), stringreader.getTotalLength());
- builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1);
- completions.forEach(builder::suggest);
+ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1);
+ completions.forEach(completion -> {
+ if (completion.tooltip() == null) {
+ builder.suggest(completion.suggestion());
+ } else {
+ builder.suggest(completion.suggestion(), PaperAdventure.asVanilla(completion.tooltip()));
+ }
+ });
com.mojang.brigadier.suggestion.Suggestions suggestions = builder.buildFuture().join();
com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getPlayer(), suggestions, buffer);
suggestEvent.setCancelled(suggestions.isEmpty());
diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
index c5e00bd9e2790992202aadf8eec2002fc88c78f1..dd8e87ad192c19743577bb95253a127072ea196c 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
@@ -29,34 +29,56 @@ public class ConsoleCommandCompleter implements Completer {
final CraftServer server = this.server.server;
final String buffer = line.line();
// Async Tab Complete
- com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event;
- java.util.List<String> completions = new java.util.ArrayList<>();
- event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(server.getConsoleSender(), completions,
- buffer, true, null);
+ final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event =
+ new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(server.getConsoleSender(), buffer, true, null);
event.callEvent();
- completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.getCompletions();
+ final List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions();
if (event.isCancelled() || event.isHandled()) {
// Still fire sync event with the provided completions, if someone is listening
if (!event.isCancelled() && TabCompleteEvent.getHandlerList().getRegisteredListeners().length > 0) {
- List<String> finalCompletions = completions;
+ List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> finalCompletions = new java.util.ArrayList<>(completions);
Waitable<List<String>> syncCompletions = new Waitable<List<String>>() {
@Override
protected List<String> evaluate() {
- org.bukkit.event.server.TabCompleteEvent syncEvent = new org.bukkit.event.server.TabCompleteEvent(server.getConsoleSender(), buffer, finalCompletions);
+ org.bukkit.event.server.TabCompleteEvent syncEvent = new org.bukkit.event.server.TabCompleteEvent(server.getConsoleSender(), buffer,
+ finalCompletions.stream()
+ .map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::suggestion)
+ .collect(java.util.stream.Collectors.toList()));
return syncEvent.callEvent() ? syncEvent.getCompletions() : com.google.common.collect.ImmutableList.of();
}
};
server.getServer().processQueue.add(syncCompletions);
try {
- completions = syncCompletions.get();
+ final List<String> legacyCompletions = syncCompletions.get();
+ completions.removeIf(it -> !legacyCompletions.contains(it.suggestion())); // remove any suggestions that were removed
+ // add any new suggestions
+ for (final String completion : legacyCompletions) {
+ if (notNewSuggestion(completions, completion)) {
+ continue;
+ }
+ completions.add(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion(completion));
+ }
} catch (InterruptedException | ExecutionException e1) {
e1.printStackTrace();
}
}
if (!completions.isEmpty()) {
- candidates.addAll(completions.stream().map(Candidate::new).collect(java.util.stream.Collectors.toList()));
+ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) {
+ if (completion.suggestion().isEmpty()) {
+ continue;
+ }
+ candidates.add(new Candidate(
+ completion.suggestion(),
+ completion.suggestion(),
+ null,
+ io.papermc.paper.adventure.PaperAdventure.PLAIN.serializeOr(completion.tooltip(), null),
+ null,
+ null,
+ false
+ ));
+ }
}
return;
}
@@ -106,4 +128,15 @@ public class ConsoleCommandCompleter implements Completer {
Thread.currentThread().interrupt();
}
}
+
+ // Paper start
+ private boolean notNewSuggestion(final List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> completions, final String completion) {
+ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion it : completions) {
+ if (it.suggestion().equals(completion)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ // Paper end
}

View file

@ -52,23 +52,22 @@ index 89eeb9d202405747409e65fcf226d95379987e29..ad87b575a0261200b280884e054a59e3
@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
index 0000000000000000000000000000000000000000..edd231d95a04a1c4832f1ca8a3da6a56e9472ca1
--- /dev/null
+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java
@@ -0,0 +1,120 @@
@@ -0,0 +1,95 @@
+package io.papermc.paper.console;
+
+import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion;
+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;
@ -77,6 +76,8 @@ index 0000000000000000000000000000000000000000..1b2681e2c0a7c5f7b386e861fecc3002
+import java.util.Collections;
+import java.util.List;
+
+import static com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion;
+
+public final class BrigadierCommandCompleter {
+ private final CommandListenerWrapper commandSourceStack;
+ private final DedicatedServer server;
@ -86,9 +87,9 @@ index 0000000000000000000000000000000000000000..1b2681e2c0a7c5f7b386e861fecc3002
+ this.commandSourceStack = commandSourceStack;
+ }
+
+ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List<Candidate> candidates, final @NonNull List<String> stringCompletions) {
+ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List<Candidate> candidates, final @NonNull List<Completion> existing) {
+ if (!com.destroystokyo.paper.PaperConfig.enableBrigadierConsoleCompletions) {
+ this.addCandidates(candidates, Collections.emptyList(), stringCompletions);
+ this.addCandidates(candidates, Collections.emptyList(), existing);
+ return;
+ }
+ final CommandDispatcher<CommandListenerWrapper> dispatcher = this.server.getCommandDispatcher().dispatcher();
@ -96,25 +97,25 @@ index 0000000000000000000000000000000000000000..1b2681e2c0a7c5f7b386e861fecc3002
+ this.addCandidates(
+ candidates,
+ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(),
+ stringCompletions
+ existing
+ );
+ }
+
+ private void addCandidates(
+ final @NonNull List<Candidate> candidates,
+ final @NonNull List<Suggestion> brigSuggestions,
+ final @NonNull List<String> stringSuggestions
+ final @NonNull List<Completion> existing
+ ) {
+ 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))) {
+ for (final Completion completion : existing) {
+ if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) {
+ continue;
+ }
+ completions.add(completion(string));
+ completions.add(completion);
+ }
+ for (final Completion completion : completions) {
+ if (completion.completion().isEmpty()) {
+ if (completion.suggestion().isEmpty()) {
+ continue;
+ }
+ candidates.add(toCandidate(completion));
@ -122,7 +123,7 @@ index 0000000000000000000000000000000000000000..1b2681e2c0a7c5f7b386e861fecc3002
+ }
+
+ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) {
+ final String suggestionText = completion.completion();
+ final String suggestionText = completion.suggestion();
+ final String suggestionTooltip = PaperAdventure.PLAIN.serializeOr(completion.tooltip(), null);
+ return new Candidate(
+ suggestionText,
@ -149,32 +150,6 @@ index 0000000000000000000000000000000000000000..1b2681e2c0a7c5f7b386e861fecc3002
+ }
+ 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
@ -275,7 +250,7 @@ index b00e5d811ddfa12937f57bac4debb2fdd057d6e1..d14e4bf09bc43e78a9da07ea062ed886
}
}
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
index dd8e87ad192c19743577bb95253a127072ea196c..5e1312941f9a554fc83adc02e81980069b8d015d 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;
@ -290,19 +265,24 @@ index c5e00bd9e2790992202aadf8eec2002fc88c78f1..bb837136285737862aa0b0f3d7fbe834
}
// Paper start - Change method signature for JLine update
@@ -55,9 +57,10 @@ public class ConsoleCommandCompleter implements Completer {
@@ -64,7 +66,7 @@ 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()));
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;
}
@@ -77,10 +80,12 @@ public class ConsoleCommandCompleter implements Completer {
@@ -99,10 +102,12 @@ public class ConsoleCommandCompleter implements Completer {
try {
List<String> offers = waitable.get();
if (offers == null) {
@ -315,23 +295,22 @@ index c5e00bd9e2790992202aadf8eec2002fc88c78f1..bb837136285737862aa0b0f3d7fbe834
for (String completion : offers) {
if (completion.isEmpty()) {
continue;
@@ -88,6 +93,8 @@ public class ConsoleCommandCompleter implements Completer {
@@ -110,6 +115,8 @@ public class ConsoleCommandCompleter implements Completer {
candidates.add(new Candidate(completion));
}
+ */
+ this.addCompletions(reader, line, candidates, offers);
+ 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
@@ -106,4 +113,10 @@ public class ConsoleCommandCompleter implements Completer {
Thread.currentThread().interrupt();
@@ -138,5 +145,9 @@ public class ConsoleCommandCompleter implements Completer {
}
return false;
}
+
+ // 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);
+ 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
// Paper end
}

View file

@ -17,10 +17,10 @@ index d68f3e6b35f0af846c8a66710c5752508c095179..0e8ee44d0104ca7c666f57bdb54e0957
return this.itemInHandIndex;
}
diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java
index 6ad075907d56a8f41ca3a7b82ff90a6d3ad9f1d4..b543776da3b799643893984a8c6f29477ed78d4a 100644
index db42f3b524adc4458c9c468b0299332e73a07a44..fd9f38f0bb723b3e78bd013ccb9538489a9ca43e 100644
--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java
+++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java
@@ -1903,6 +1903,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
@@ -1908,6 +1908,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
PlayerConnectionUtils.ensureMainThread(packetplayinhelditemslot, this, this.player.getWorldServer());
if (this.player.isFrozen()) return; // CraftBukkit
if (packetplayinhelditemslot.b() >= 0 && packetplayinhelditemslot.b() < PlayerInventory.getHotbarSize()) {

View file

@ -5,10 +5,10 @@ Subject: [PATCH] add RespawnFlags to PlayerRespawnEvent
diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java
index b543776da3b799643893984a8c6f29477ed78d4a..9455cb9bc849a330e57fdc466fb51902631e22d8 100644
index fd9f38f0bb723b3e78bd013ccb9538489a9ca43e..8c9e97bb093c0e6297397edc71d72deebbcfbed9 100644
--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java
+++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java
@@ -2436,7 +2436,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
@@ -2441,7 +2441,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
case PERFORM_RESPAWN:
if (this.player.viewingCredits) {
this.player.viewingCredits = false;