mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-29 19:52:55 +01:00
407 lines
14 KiB
Diff
407 lines
14 KiB
Diff
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 a135a9bfb2ccc8842baa9d5760fa05b7b1a529b1..9be64a95c2345433b6142d611077dedadcef9f5d 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.base.Preconditions;
|
|
+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.bukkit.Location;
|
|
import org.bukkit.command.Command;
|
|
import org.bukkit.command.CommandSender;
|
|
@@ -33,6 +38,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;
|
|
|
|
@@ -47,15 +53,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;
|
|
@@ -83,7 +103,7 @@ public class AsyncTabCompleteEvent extends Event implements Cancellable {
|
|
*/
|
|
@NotNull
|
|
public List<String> getCompletions() {
|
|
- return completions;
|
|
+ return this.stringCompletions;
|
|
}
|
|
|
|
/**
|
|
@@ -97,8 +117,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;
|
|
+ }
|
|
Preconditions.checkNotNull(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<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<Completion> newCompletions) {
|
|
+ Preconditions.checkNotNull(newCompletions, "new completions");
|
|
+ this.completions.clear();
|
|
+ this.completions.addAll(newCompletions);
|
|
}
|
|
|
|
/**
|
|
@@ -173,4 +227,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 93498307004b68b934fbfa1aeb3aaf0e97cbdac7..bbe81f7a420f913ffdcad913a3c43ff41ead41f5 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",
|