From 7bc482249f3c49c1ef52f39ac73c48e83e125ad5 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Fri, 7 May 2021 16:12:03 -0700 Subject: [PATCH] Allow for Component suggestion tooltips in AsyncTabCompleteEvent (#5504) --- ...ent-suggestion-tooltips-in-AsyncTabC.patch | 407 ++++++++++++++++++ ...ent-suggestion-tooltips-in-AsyncTabC.patch | 132 ++++++ ...tab-completions-for-brigadier-comman.patch | 67 +-- 3 files changed, 562 insertions(+), 44 deletions(-) create mode 100644 Spigot-API-Patches/Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch create mode 100644 Spigot-Server-Patches/Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch diff --git a/Spigot-API-Patches/Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch b/Spigot-API-Patches/Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch new file mode 100644 index 0000000000..8385584cf5 --- /dev/null +++ b/Spigot-API-Patches/Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch @@ -0,0 +1,407 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java +@@ -0,0 +0,0 @@ + 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; +@@ -0,0 +0,0 @@ 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; + +@@ -0,0 +0,0 @@ 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; +@@ -0,0 +0,0 @@ public class AsyncTabCompleteEvent extends Event implements Cancellable { + */ + @NotNull + public List<String> getCompletions() { +- return completions; ++ return this.stringCompletions; + } + + /** +@@ -0,0 +0,0 @@ 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); + } + + /** +@@ -0,0 +0,0 @@ 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..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/TransformingRandomAccessList.java +@@ -0,0 +0,0 @@ ++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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/test/java/org/bukkit/AnnotationTest.java ++++ b/src/test/java/org/bukkit/AnnotationTest.java +@@ -0,0 +0,0 @@ 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", diff --git a/Spigot-Server-Patches/Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch b/Spigot-Server-Patches/Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch new file mode 100644 index 0000000000..6ce190411e --- /dev/null +++ b/Spigot-Server-Patches/Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch @@ -0,0 +1,132 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -0,0 +0,0 @@ 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()) { +@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -0,0 +0,0 @@ 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; + } +@@ -0,0 +0,0 @@ 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 + } diff --git a/Spigot-Server-Patches/Enhance-console-tab-completions-for-brigadier-comman.patch b/Spigot-Server-Patches/Enhance-console-tab-completions-for-brigadier-comman.patch index 9681922d79..80071ff2d5 100644 --- a/Spigot-Server-Patches/Enhance-console-tab-completions-for-brigadier-comman.patch +++ b/Spigot-Server-Patches/Enhance-console-tab-completions-for-brigadier-comman.patch @@ -58,17 +58,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +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..00000000000000000000000000000000 +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..00000000000000000000000000000000 + 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..00000000000000000000000000000000 + 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..00000000000000000000000000000000 + } + + 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..00000000000000000000000000000000 + } + 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 @@ -296,7 +271,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - 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; +@@ -0,0 +0,0 @@ public class ConsoleCommandCompleter implements Completer { + )); + } } + this.addCompletions(reader, line, candidates, completions); return; @@ -320,18 +300,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 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 @@ -0,0 +0,0 @@ public class ConsoleCommandCompleter implements Completer { - Thread.currentThread().interrupt(); } + 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 }