diff options
8 files changed, 135 insertions, 53 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index e46db663d..33d7cdbe9 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -101,6 +101,7 @@ import at.hannibal2.skyhanni.features.commands.WarpIsCommand import at.hannibal2.skyhanni.features.commands.WikiManager import at.hannibal2.skyhanni.features.commands.tabcomplete.GetFromSacksTabComplete import at.hannibal2.skyhanni.features.commands.tabcomplete.PlayerTabComplete +import at.hannibal2.skyhanni.features.commands.tabcomplete.TabComplete import at.hannibal2.skyhanni.features.commands.tabcomplete.WarpTabComplete import at.hannibal2.skyhanni.features.cosmetics.ArrowTrail import at.hannibal2.skyhanni.features.cosmetics.CosmeticFollowingLine @@ -530,6 +531,7 @@ class SkyHanniMod { loadModule(FixedRateTimerManager()) loadModule(ChromaManager) loadModule(ContributorManager) + loadModule(TabComplete) // APIs loadModule(BazaarApi()) diff --git a/src/main/java/at/hannibal2/skyhanni/events/TabCompletionEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/TabCompletionEvent.kt new file mode 100644 index 000000000..f3ec47b02 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/TabCompletionEvent.kt @@ -0,0 +1,37 @@ +package at.hannibal2.skyhanni.events + +class TabCompletionEvent( + val leftOfCursor: String, + val fullText: String, + val originalCompletions: List<String>, +) : LorenzEvent() { + val lastWord = leftOfCursor.substringAfterLast(' ') + val additionalSuggestions = mutableSetOf<String>() + val suppressedSuggestions = mutableSetOf<String>() + + fun addSuggestion(suggestion: String) { + if (suggestion.startsWith(lastWord, ignoreCase = true)) + additionalSuggestions.add(suggestion) + } + + fun addSuggestions(suggestions: Iterable<String>) { + suggestions.forEach(this::addSuggestion) + } + + fun excludeAllDefault() { + suppressedSuggestions.addAll(originalCompletions) + } + + val command = if (leftOfCursor.startsWith("/")) + leftOfCursor.substring(1).substringBefore(" ").lowercase() + else "" + + fun isCommand(commandName: String): Boolean { + return commandName.equals(command, ignoreCase = true) + } + + fun intoSuggestionArray(): Array<String>? { + if (additionalSuggestions.isEmpty() && suppressedSuggestions.isEmpty()) return null + return (originalCompletions - suppressedSuggestions + additionalSuggestions).toTypedArray() + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/PartyCommands.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/PartyCommands.kt index 5196b08db..b323bb73d 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/commands/PartyCommands.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/PartyCommands.kt @@ -90,7 +90,7 @@ object PartyCommands { if (command == "p" || command == "party") { val friends = if (config.tabComplete.friends) { - FriendAPI.getAllFriends().filter { it.bestFriend || config.tabComplete.onlyBestFriends }.map { it.name } + FriendAPI.getAllFriends().filter { it.bestFriend || !config.tabComplete.onlyBestFriends }.map { it.name } } else { emptyList<String>() } diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt index a835efd38..49bde5993 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt @@ -1,29 +1,29 @@ package at.hannibal2.skyhanni.features.commands.tabcomplete +import at.hannibal2.skyhanni.events.TabCompletionEvent import at.hannibal2.skyhanni.features.commands.PartyCommands import at.hannibal2.skyhanni.features.commands.ViewRecipeCommand import at.hannibal2.skyhanni.features.misc.CollectionTracker +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent object TabComplete { - @JvmStatic - fun handleTabComplete(leftOfCursor: String, originalArray: Array<String>): Array<String>? { - val splits = leftOfCursor.split(" ") + @SubscribeEvent + fun handleTabComplete(event: TabCompletionEvent) { + val splits = event.leftOfCursor.split(" ") if (splits.size > 1) { var command = splits.first().lowercase() if (command.startsWith("/")) { command = command.substring(1) customTabComplete(command)?.let { - return buildResponse(splits, it).toSet().toTypedArray() + event.addSuggestions(it) } } } - return null } private fun customTabComplete(command: String): List<String>? { GetFromSacksTabComplete.handleTabComplete(command)?.let { return it } - WarpTabComplete.handleTabComplete(command)?.let { return it } PlayerTabComplete.handleTabComplete(command)?.let { return it } CollectionTracker.handleTabComplete(command)?.let { return it } PartyCommands.customTabComplete(command)?.let { return it } diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/WarpTabComplete.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/WarpTabComplete.kt index 4651040b8..4520aee09 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/WarpTabComplete.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/WarpTabComplete.kt @@ -3,6 +3,7 @@ package at.hannibal2.skyhanni.features.commands.tabcomplete import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.data.jsonobjects.repo.WarpsJson import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.events.TabCompletionEvent import at.hannibal2.skyhanni.utils.LorenzUtils import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -17,11 +18,11 @@ object WarpTabComplete { warps = data.warpCommands } - fun handleTabComplete(command: String): List<String>? { - if (!isEnabled()) return null - if (command != "warp") return null - - return warps + @SubscribeEvent + fun onTabComplete(event: TabCompletionEvent) { + if (event.isCommand("warp")) { + event.addSuggestions(warps) + } } fun isEnabled() = LorenzUtils.inSkyBlock && config.warps diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/init/BeforeForLoopInjectionPoint.java b/src/main/java/at/hannibal2/skyhanni/mixins/init/BeforeForLoopInjectionPoint.java new file mode 100644 index 000000000..5316a8258 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/mixins/init/BeforeForLoopInjectionPoint.java @@ -0,0 +1,59 @@ +package at.hannibal2.skyhanni.mixins.init; + +import org.spongepowered.asm.lib.Opcodes; +import org.spongepowered.asm.lib.tree.AbstractInsnNode; +import org.spongepowered.asm.lib.tree.InsnList; +import org.spongepowered.asm.lib.tree.VarInsnNode; +import org.spongepowered.asm.mixin.injection.InjectionPoint; +import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; + +import java.util.Collection; + +/** + * Inject just before a for loop which iterates over a local variable containing an array. + * + * <pre>{@code + * String [] s = new String[10]; + * + * // <-- Injection point here + * for (String e : s) { + * + * } + * } + * </pre> + * Does not work for more complex instructions which call functions or do other operations inside of the for loop header. + * Does not work for {@link java.util.Iterator iterators}. + * + * <p>Set the lvIndex arg to specify which lvIndex to search for when selecting the loop.</p> + * + * + * <pre>{@code + * @Inject(method = "...", at = @At(value = "SKYHANNI_FORLOOP_LOCAL_VAR", args = "lvIndex=1")) + * }</pre> + */ +@InjectionPoint.AtCode("SKYHANNI_FORLOOP_LOCAL_VAR") +public class BeforeForLoopInjectionPoint extends InjectionPoint { + private final int lvIndex; + + public BeforeForLoopInjectionPoint(InjectionPointData data) { + lvIndex = data.get("lvIndex", -1); + } + + @Override + public boolean find(String s, InsnList insnList, Collection<AbstractInsnNode> collection) { + for (AbstractInsnNode p = insnList.getFirst(); p != null; p = p.getNext()) { + if (p.getOpcode() != Opcodes.ARRAYLENGTH) { + continue; + } + AbstractInsnNode loadLoopVar = p.getPrevious(); + if (loadLoopVar == null || loadLoopVar.getOpcode() != Opcodes.ALOAD) continue; + AbstractInsnNode storeLoopVar = loadLoopVar.getPrevious(); + if (storeLoopVar == null || storeLoopVar.getOpcode() != Opcodes.ASTORE) continue; + AbstractInsnNode loadLoopArg = storeLoopVar.getPrevious(); + if (loadLoopArg == null || loadLoopArg.getOpcode() != Opcodes.ALOAD) continue; + if (lvIndex != -1 && ((VarInsnNode) loadLoopArg).var != lvIndex) continue; + collection.add(loadLoopArg); + } + return false; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/init/SkyhanniMixinPlugin.java b/src/main/java/at/hannibal2/skyhanni/mixins/init/SkyhanniMixinPlugin.java index 1f1b0e1fe..9dd4cc6f8 100644 --- a/src/main/java/at/hannibal2/skyhanni/mixins/init/SkyhanniMixinPlugin.java +++ b/src/main/java/at/hannibal2/skyhanni/mixins/init/SkyhanniMixinPlugin.java @@ -3,6 +3,7 @@ package at.hannibal2.skyhanni.mixins.init; import org.spongepowered.asm.lib.tree.ClassNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import org.spongepowered.asm.mixin.injection.InjectionPoint; import java.io.IOException; import java.net.MalformedURLException; @@ -21,7 +22,7 @@ import java.util.zip.ZipInputStream; public class SkyhanniMixinPlugin implements IMixinConfigPlugin { @Override public void onLoad(String mixinPackage) { - + InjectionPoint.register(BeforeForLoopInjectionPoint.class); } @Override diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/transformers/MixinGuiChat.java b/src/main/java/at/hannibal2/skyhanni/mixins/transformers/MixinGuiChat.java index 97ef80364..ff744b7d0 100644 --- a/src/main/java/at/hannibal2/skyhanni/mixins/transformers/MixinGuiChat.java +++ b/src/main/java/at/hannibal2/skyhanni/mixins/transformers/MixinGuiChat.java @@ -1,24 +1,22 @@ package at.hannibal2.skyhanni.mixins.transformers; import at.hannibal2.skyhanni.events.ChatHoverEvent; -import at.hannibal2.skyhanni.features.commands.tabcomplete.TabComplete; +import at.hannibal2.skyhanni.events.TabCompletionEvent; import at.hannibal2.skyhanni.mixins.hooks.GuiChatHook; -import com.google.common.collect.Lists; import net.minecraft.client.gui.GuiChat; import net.minecraft.client.gui.GuiTextField; import net.minecraft.util.ChatComponentText; -import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.IChatComponent; -import org.apache.commons.lang3.StringUtils; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; -import java.util.List; +import java.util.Arrays; @Mixin(GuiChat.class) public class MixinGuiChat { @@ -26,41 +24,25 @@ public class MixinGuiChat { @Shadow protected GuiTextField inputField; - @Shadow - private boolean waitingOnAutocomplete; - - @Shadow - private boolean playerNamesFound; - - @Shadow - private List<String> foundPlayerNames = Lists.newArrayList(); - - @Inject(method = "onAutocompleteResponse", at = @At(value = "HEAD"), cancellable = true) - private void renderItemOverlayPost(String[] originalArray, CallbackInfo ci) { - - if (this.waitingOnAutocomplete) { - String[] result = TabComplete.handleTabComplete(this.inputField.getText(), originalArray); - if (result == null) return; - ci.cancel(); - - this.playerNamesFound = false; - this.foundPlayerNames.clear(); - for (String s : result) { - if (!s.isEmpty()) { - this.foundPlayerNames.add(s); - } - } - - String s1 = this.inputField.getText().substring(this.inputField.func_146197_a(-1, this.inputField.getCursorPosition(), false)); - String s2 = StringUtils.getCommonPrefix(result); - s2 = EnumChatFormatting.getTextWithoutFormattingCodes(s2); - if (!s2.isEmpty() && !s1.equalsIgnoreCase(s2)) { - this.inputField.deleteFromCursor(this.inputField.func_146197_a(-1, this.inputField.getCursorPosition(), false) - this.inputField.getCursorPosition()); - this.inputField.writeText(s2); - } else if (!this.foundPlayerNames.isEmpty()) { - this.playerNamesFound = true; - } - } + @ModifyVariable( + method = "onAutocompleteResponse", + at = @At( + value = "SKYHANNI_FORLOOP_LOCAL_VAR", + shift = At.Shift.BEFORE, + args = "lvIndex=1" + ), + index = 1, + argsOnly = true + ) + private String[] renderItemOverlayPost(String[] originalArray) { + String inputFieldText = this.inputField.getText(); + String beforeCursor = inputFieldText.substring(0, this.inputField.getCursorPosition()); + TabCompletionEvent tabCompletionEvent = new TabCompletionEvent(beforeCursor, inputFieldText, Arrays.asList(originalArray)); + tabCompletionEvent.postAndCatch(); + String[] newSuggestions = tabCompletionEvent.intoSuggestionArray(); + if (newSuggestions == null) + newSuggestions = originalArray; + return newSuggestions; } @Inject(method = "drawScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiChat;handleComponentHover(Lnet/minecraft/util/IChatComponent;II)V"), locals = LocalCapture.CAPTURE_FAILHARD) |