From 29785297b5f7db6a99a48435b8814c9ff35e6d52 Mon Sep 17 00:00:00 2001 From: Jason 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 ++++++ ...ab-completions-for-brigadier-comman.patch} | 81 ++-- ...temConsumeEvent-cancelling-properly.patch} | 0 ...patch => 0703-Add-bypass-host-check.patch} | 0 ...on-t-throw-when-loading-invalid-TEs.patch} | 0 ...0705-Set-area-affect-cloud-rotation.patch} | 0 ...add-isDeeplySleeping-to-HumanEntity.patch} | 0 ...ting-give-items-on-item-drop-cancel.patch} | 0 ...add-consumeFuel-to-FurnaceBurnEvent.patch} | 0 ...-set-drop-chance-to-EntityEquipment.patch} | 0 ...ix-PigZombieAngerEvent-cancellation.patch} | 0 ...-checkReach-check-for-Shulker-boxes.patch} | 0 ...ix-PlayerItemHeldEvent-firing-twice.patch} | 4 +- ... => 0713-Added-PlayerDeepSleepEvent.patch} | 0 ...ld-API.patch => 0714-More-World-API.patch} | 0 ... 0715-Added-PlayerBedFailEnterEvent.patch} | 0 ...-to-convert-between-Component-and-B.patch} | 0 ...n-acting-as-a-bed-respawn-from-the-.patch} | 0 ...-RespawnFlags-to-PlayerRespawnEvent.patch} | 4 +- ...acon-activation-deactivation-events.patch} | 0 ...dd-Channel-initialization-listeners.patch} | 0 ...mands-if-tab-completion-is-disabled.patch} | 0 ...> 0722-Add-more-WanderingTrader-API.patch} | 0 24 files changed, 573 insertions(+), 55 deletions(-) create mode 100644 Spigot-API-Patches/0295-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch create mode 100644 Spigot-Server-Patches/0700-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch rename Spigot-Server-Patches/{0700-Enhance-console-tab-completions-for-brigadier-comman.patch => 0701-Enhance-console-tab-completions-for-brigadier-comman.patch} (86%) rename Spigot-Server-Patches/{0701-Fix-PlayerItemConsumeEvent-cancelling-properly.patch => 0702-Fix-PlayerItemConsumeEvent-cancelling-properly.patch} (100%) rename Spigot-Server-Patches/{0702-Add-bypass-host-check.patch => 0703-Add-bypass-host-check.patch} (100%) rename Spigot-Server-Patches/{0703-don-t-throw-when-loading-invalid-TEs.patch => 0704-don-t-throw-when-loading-invalid-TEs.patch} (100%) rename Spigot-Server-Patches/{0704-Set-area-affect-cloud-rotation.patch => 0705-Set-area-affect-cloud-rotation.patch} (100%) rename Spigot-Server-Patches/{0705-add-isDeeplySleeping-to-HumanEntity.patch => 0706-add-isDeeplySleeping-to-HumanEntity.patch} (100%) rename Spigot-Server-Patches/{0706-Fix-duplicating-give-items-on-item-drop-cancel.patch => 0707-Fix-duplicating-give-items-on-item-drop-cancel.patch} (100%) rename Spigot-Server-Patches/{0707-add-consumeFuel-to-FurnaceBurnEvent.patch => 0708-add-consumeFuel-to-FurnaceBurnEvent.patch} (100%) rename Spigot-Server-Patches/{0708-add-get-set-drop-chance-to-EntityEquipment.patch => 0709-add-get-set-drop-chance-to-EntityEquipment.patch} (100%) rename Spigot-Server-Patches/{0709-fix-PigZombieAngerEvent-cancellation.patch => 0710-fix-PigZombieAngerEvent-cancellation.patch} (100%) rename Spigot-Server-Patches/{0710-Fix-checkReach-check-for-Shulker-boxes.patch => 0711-Fix-checkReach-check-for-Shulker-boxes.patch} (100%) rename Spigot-Server-Patches/{0711-fix-PlayerItemHeldEvent-firing-twice.patch => 0712-fix-PlayerItemHeldEvent-firing-twice.patch} (92%) rename Spigot-Server-Patches/{0712-Added-PlayerDeepSleepEvent.patch => 0713-Added-PlayerDeepSleepEvent.patch} (100%) rename Spigot-Server-Patches/{0713-More-World-API.patch => 0714-More-World-API.patch} (100%) rename Spigot-Server-Patches/{0714-Added-PlayerBedFailEnterEvent.patch => 0715-Added-PlayerBedFailEnterEvent.patch} (100%) rename Spigot-Server-Patches/{0715-Implement-methods-to-convert-between-Component-and-B.patch => 0716-Implement-methods-to-convert-between-Component-and-B.patch} (100%) rename Spigot-Server-Patches/{0716-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch => 0717-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch} (100%) rename Spigot-Server-Patches/{0717-add-RespawnFlags-to-PlayerRespawnEvent.patch => 0718-add-RespawnFlags-to-PlayerRespawnEvent.patch} (95%) rename Spigot-Server-Patches/{0718-Introduce-beacon-activation-deactivation-events.patch => 0719-Introduce-beacon-activation-deactivation-events.patch} (100%) rename Spigot-Server-Patches/{0719-Add-Channel-initialization-listeners.patch => 0720-Add-Channel-initialization-listeners.patch} (100%) rename Spigot-Server-Patches/{0720-Send-empty-commands-if-tab-completion-is-disabled.patch => 0721-Send-empty-commands-if-tab-completion-is-disabled.patch} (100%) rename Spigot-Server-Patches/{0721-Add-more-WanderingTrader-API.patch => 0722-Add-more-WanderingTrader-API.patch} (100%) diff --git a/Spigot-API-Patches/0295-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch b/Spigot-API-Patches/0295-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch new file mode 100644 index 000000000..9908089e1 --- /dev/null +++ b/Spigot-API-Patches/0295-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch @@ -0,0 +1,407 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +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 completions; ++ private final List completions = new ArrayList<>(); ++ private final List 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 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 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 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. ++ *

++ * 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. ++ *

++ * 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 fromStrings(final @NotNull List strings) { ++ final List 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 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}. ++ * ++ *

If the provided component is null, the suggestion will not have a tooltip.

++ * ++ * @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 backing list element type ++ * @param transformed list element type ++ */ ++public final class TransformingRandomAccessList extends AbstractList implements RandomAccess { ++ final List fromList; ++ final Function toFunction; ++ final Function 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 fromList, ++ final @NonNull Function toFunction, ++ final @NonNull Function 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 iterator() { ++ return this.listIterator(); ++ } ++ ++ @Override ++ public @NotNull ListIterator listIterator(int index) { ++ return new TransformedListIterator(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 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 implements ListIterator, Iterator { ++ final Iterator backingIterator; ++ ++ TransformedListIterator(ListIterator backingIterator) { ++ this.backingIterator = checkNotNull((Iterator) backingIterator); ++ } ++ ++ private ListIterator backingIterator() { ++ return cast(this.backingIterator); ++ } ++ ++ static ListIterator cast(Iterator iterator) { ++ return (ListIterator) 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", diff --git a/Spigot-Server-Patches/0700-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch b/Spigot-Server-Patches/0700-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch new file mode 100644 index 000000000..fef6b7dd9 --- /dev/null +++ b/Spigot-Server-Patches/0700-Allow-for-Component-suggestion-tooltips-in-AsyncTabC.patch @@ -0,0 +1,132 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +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 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 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 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 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 finalCompletions = completions; ++ List finalCompletions = new java.util.ArrayList<>(completions); + Waitable> syncCompletions = new Waitable>() { + @Override + protected List 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 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 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/0700-Enhance-console-tab-completions-for-brigadier-comman.patch b/Spigot-Server-Patches/0701-Enhance-console-tab-completions-for-brigadier-comman.patch similarity index 86% rename from Spigot-Server-Patches/0700-Enhance-console-tab-completions-for-brigadier-comman.patch rename to Spigot-Server-Patches/0701-Enhance-console-tab-completions-for-brigadier-comman.patch index 6a4e84db9..5636a8b6e 100644 --- a/Spigot-Server-Patches/0700-Enhance-console-tab-completions-for-brigadier-comman.patch +++ b/Spigot-Server-Patches/0701-Enhance-console-tab-completions-for-brigadier-comman.patch @@ -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 candidates, final @NonNull List stringCompletions) { ++ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List candidates, final @NonNull List existing) { + if (!com.destroystokyo.paper.PaperConfig.enableBrigadierConsoleCompletions) { -+ this.addCandidates(candidates, Collections.emptyList(), stringCompletions); ++ this.addCandidates(candidates, Collections.emptyList(), existing); + return; + } + final CommandDispatcher 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 candidates, + final @NonNull List brigSuggestions, -+ final @NonNull List stringSuggestions ++ final @NonNull List existing + ) { + final List 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 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 candidates, final List stringCompletions) { -+ this.brigadierCompleter.complete(reader, line, candidates, stringCompletions); ++ private void addCompletions(final LineReader reader, final ParsedLine line, final List candidates, final List existing) { ++ this.brigadierCompleter.complete(reader, line, candidates, existing); + } -+ // Paper end + // Paper end } diff --git a/Spigot-Server-Patches/0701-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/Spigot-Server-Patches/0702-Fix-PlayerItemConsumeEvent-cancelling-properly.patch similarity index 100% rename from Spigot-Server-Patches/0701-Fix-PlayerItemConsumeEvent-cancelling-properly.patch rename to Spigot-Server-Patches/0702-Fix-PlayerItemConsumeEvent-cancelling-properly.patch diff --git a/Spigot-Server-Patches/0702-Add-bypass-host-check.patch b/Spigot-Server-Patches/0703-Add-bypass-host-check.patch similarity index 100% rename from Spigot-Server-Patches/0702-Add-bypass-host-check.patch rename to Spigot-Server-Patches/0703-Add-bypass-host-check.patch diff --git a/Spigot-Server-Patches/0703-don-t-throw-when-loading-invalid-TEs.patch b/Spigot-Server-Patches/0704-don-t-throw-when-loading-invalid-TEs.patch similarity index 100% rename from Spigot-Server-Patches/0703-don-t-throw-when-loading-invalid-TEs.patch rename to Spigot-Server-Patches/0704-don-t-throw-when-loading-invalid-TEs.patch diff --git a/Spigot-Server-Patches/0704-Set-area-affect-cloud-rotation.patch b/Spigot-Server-Patches/0705-Set-area-affect-cloud-rotation.patch similarity index 100% rename from Spigot-Server-Patches/0704-Set-area-affect-cloud-rotation.patch rename to Spigot-Server-Patches/0705-Set-area-affect-cloud-rotation.patch diff --git a/Spigot-Server-Patches/0705-add-isDeeplySleeping-to-HumanEntity.patch b/Spigot-Server-Patches/0706-add-isDeeplySleeping-to-HumanEntity.patch similarity index 100% rename from Spigot-Server-Patches/0705-add-isDeeplySleeping-to-HumanEntity.patch rename to Spigot-Server-Patches/0706-add-isDeeplySleeping-to-HumanEntity.patch diff --git a/Spigot-Server-Patches/0706-Fix-duplicating-give-items-on-item-drop-cancel.patch b/Spigot-Server-Patches/0707-Fix-duplicating-give-items-on-item-drop-cancel.patch similarity index 100% rename from Spigot-Server-Patches/0706-Fix-duplicating-give-items-on-item-drop-cancel.patch rename to Spigot-Server-Patches/0707-Fix-duplicating-give-items-on-item-drop-cancel.patch diff --git a/Spigot-Server-Patches/0707-add-consumeFuel-to-FurnaceBurnEvent.patch b/Spigot-Server-Patches/0708-add-consumeFuel-to-FurnaceBurnEvent.patch similarity index 100% rename from Spigot-Server-Patches/0707-add-consumeFuel-to-FurnaceBurnEvent.patch rename to Spigot-Server-Patches/0708-add-consumeFuel-to-FurnaceBurnEvent.patch diff --git a/Spigot-Server-Patches/0708-add-get-set-drop-chance-to-EntityEquipment.patch b/Spigot-Server-Patches/0709-add-get-set-drop-chance-to-EntityEquipment.patch similarity index 100% rename from Spigot-Server-Patches/0708-add-get-set-drop-chance-to-EntityEquipment.patch rename to Spigot-Server-Patches/0709-add-get-set-drop-chance-to-EntityEquipment.patch diff --git a/Spigot-Server-Patches/0709-fix-PigZombieAngerEvent-cancellation.patch b/Spigot-Server-Patches/0710-fix-PigZombieAngerEvent-cancellation.patch similarity index 100% rename from Spigot-Server-Patches/0709-fix-PigZombieAngerEvent-cancellation.patch rename to Spigot-Server-Patches/0710-fix-PigZombieAngerEvent-cancellation.patch diff --git a/Spigot-Server-Patches/0710-Fix-checkReach-check-for-Shulker-boxes.patch b/Spigot-Server-Patches/0711-Fix-checkReach-check-for-Shulker-boxes.patch similarity index 100% rename from Spigot-Server-Patches/0710-Fix-checkReach-check-for-Shulker-boxes.patch rename to Spigot-Server-Patches/0711-Fix-checkReach-check-for-Shulker-boxes.patch diff --git a/Spigot-Server-Patches/0711-fix-PlayerItemHeldEvent-firing-twice.patch b/Spigot-Server-Patches/0712-fix-PlayerItemHeldEvent-firing-twice.patch similarity index 92% rename from Spigot-Server-Patches/0711-fix-PlayerItemHeldEvent-firing-twice.patch rename to Spigot-Server-Patches/0712-fix-PlayerItemHeldEvent-firing-twice.patch index 5c4f22712..9d3a5dd90 100644 --- a/Spigot-Server-Patches/0711-fix-PlayerItemHeldEvent-firing-twice.patch +++ b/Spigot-Server-Patches/0712-fix-PlayerItemHeldEvent-firing-twice.patch @@ -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()) { diff --git a/Spigot-Server-Patches/0712-Added-PlayerDeepSleepEvent.patch b/Spigot-Server-Patches/0713-Added-PlayerDeepSleepEvent.patch similarity index 100% rename from Spigot-Server-Patches/0712-Added-PlayerDeepSleepEvent.patch rename to Spigot-Server-Patches/0713-Added-PlayerDeepSleepEvent.patch diff --git a/Spigot-Server-Patches/0713-More-World-API.patch b/Spigot-Server-Patches/0714-More-World-API.patch similarity index 100% rename from Spigot-Server-Patches/0713-More-World-API.patch rename to Spigot-Server-Patches/0714-More-World-API.patch diff --git a/Spigot-Server-Patches/0714-Added-PlayerBedFailEnterEvent.patch b/Spigot-Server-Patches/0715-Added-PlayerBedFailEnterEvent.patch similarity index 100% rename from Spigot-Server-Patches/0714-Added-PlayerBedFailEnterEvent.patch rename to Spigot-Server-Patches/0715-Added-PlayerBedFailEnterEvent.patch diff --git a/Spigot-Server-Patches/0715-Implement-methods-to-convert-between-Component-and-B.patch b/Spigot-Server-Patches/0716-Implement-methods-to-convert-between-Component-and-B.patch similarity index 100% rename from Spigot-Server-Patches/0715-Implement-methods-to-convert-between-Component-and-B.patch rename to Spigot-Server-Patches/0716-Implement-methods-to-convert-between-Component-and-B.patch diff --git a/Spigot-Server-Patches/0716-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch b/Spigot-Server-Patches/0717-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch similarity index 100% rename from Spigot-Server-Patches/0716-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch rename to Spigot-Server-Patches/0717-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch diff --git a/Spigot-Server-Patches/0717-add-RespawnFlags-to-PlayerRespawnEvent.patch b/Spigot-Server-Patches/0718-add-RespawnFlags-to-PlayerRespawnEvent.patch similarity index 95% rename from Spigot-Server-Patches/0717-add-RespawnFlags-to-PlayerRespawnEvent.patch rename to Spigot-Server-Patches/0718-add-RespawnFlags-to-PlayerRespawnEvent.patch index cc5ab87b4..f74833a61 100644 --- a/Spigot-Server-Patches/0717-add-RespawnFlags-to-PlayerRespawnEvent.patch +++ b/Spigot-Server-Patches/0718-add-RespawnFlags-to-PlayerRespawnEvent.patch @@ -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; diff --git a/Spigot-Server-Patches/0718-Introduce-beacon-activation-deactivation-events.patch b/Spigot-Server-Patches/0719-Introduce-beacon-activation-deactivation-events.patch similarity index 100% rename from Spigot-Server-Patches/0718-Introduce-beacon-activation-deactivation-events.patch rename to Spigot-Server-Patches/0719-Introduce-beacon-activation-deactivation-events.patch diff --git a/Spigot-Server-Patches/0719-Add-Channel-initialization-listeners.patch b/Spigot-Server-Patches/0720-Add-Channel-initialization-listeners.patch similarity index 100% rename from Spigot-Server-Patches/0719-Add-Channel-initialization-listeners.patch rename to Spigot-Server-Patches/0720-Add-Channel-initialization-listeners.patch diff --git a/Spigot-Server-Patches/0720-Send-empty-commands-if-tab-completion-is-disabled.patch b/Spigot-Server-Patches/0721-Send-empty-commands-if-tab-completion-is-disabled.patch similarity index 100% rename from Spigot-Server-Patches/0720-Send-empty-commands-if-tab-completion-is-disabled.patch rename to Spigot-Server-Patches/0721-Send-empty-commands-if-tab-completion-is-disabled.patch diff --git a/Spigot-Server-Patches/0721-Add-more-WanderingTrader-API.patch b/Spigot-Server-Patches/0722-Add-more-WanderingTrader-API.patch similarity index 100% rename from Spigot-Server-Patches/0721-Add-more-WanderingTrader-API.patch rename to Spigot-Server-Patches/0722-Add-more-WanderingTrader-API.patch