소스 검색

Improved Search

Signed-off-by: shedaniel <daniel@shedaniel.me>
shedaniel 5 년 전
부모
커밋
c0295ec8a1
28개의 변경된 파일752개의 추가작업 그리고 387개의 파일을 삭제
  1. 1 1
      src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java
  2. 11 2
      src/main/java/me/shedaniel/rei/api/EntryStack.java
  3. 25 0
      src/main/java/me/shedaniel/rei/api/TextRepresentable.java
  4. 13 0
      src/main/java/me/shedaniel/rei/api/TextTest.java
  5. 7 1
      src/main/java/me/shedaniel/rei/api/fluid/FluidSupportProvider.java
  6. 2 2
      src/main/java/me/shedaniel/rei/gui/OverlaySearchField.java
  7. 1 2
      src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java
  8. 1 6
      src/main/java/me/shedaniel/rei/gui/VillagerRecipeViewingScreen.java
  9. 8 3
      src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java
  10. 7 2
      src/main/java/me/shedaniel/rei/gui/widget/FavoritesListWidget.java
  11. 121 119
      src/main/java/me/shedaniel/rei/gui/widget/TextFieldWidget.java
  12. 19 23
      src/main/java/me/shedaniel/rei/impl/FluidEntryStack.java
  13. 0 12
      src/main/java/me/shedaniel/rei/impl/FluidSupportProviderImpl.java
  14. 37 1
      src/main/java/me/shedaniel/rei/impl/ItemEntryStack.java
  15. 1 1
      src/main/java/me/shedaniel/rei/impl/RenderingEntry.java
  16. 49 203
      src/main/java/me/shedaniel/rei/impl/SearchArgument.java
  17. 62 0
      src/main/java/me/shedaniel/rei/impl/SimpleFluidRenderer.java
  18. 27 0
      src/main/java/me/shedaniel/rei/impl/search/AlwaysMatchingArgument.java
  19. 41 0
      src/main/java/me/shedaniel/rei/impl/search/Argument.java
  20. 20 0
      src/main/java/me/shedaniel/rei/impl/search/ArgumentsRegistry.java
  21. 63 0
      src/main/java/me/shedaniel/rei/impl/search/MatchStatus.java
  22. 18 0
      src/main/java/me/shedaniel/rei/impl/search/MatchType.java
  23. 43 0
      src/main/java/me/shedaniel/rei/impl/search/ModArgument.java
  24. 56 0
      src/main/java/me/shedaniel/rei/impl/search/RegexArgument.java
  25. 51 0
      src/main/java/me/shedaniel/rei/impl/search/TagArgument.java
  26. 33 0
      src/main/java/me/shedaniel/rei/impl/search/TextArgument.java
  27. 35 0
      src/main/java/me/shedaniel/rei/impl/search/TooltipArgument.java
  28. 0 9
      src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java

+ 1 - 1
src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java

@@ -192,7 +192,7 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer {
             ClientSidePacketRegistry.INSTANCE.register(RoughlyEnoughItemsNetwork.CREATE_ITEMS_MESSAGE_PACKET, (packetContext, packetByteBuf) -> {
                 ItemStack stack = packetByteBuf.readItemStack();
                 String player = packetByteBuf.readString(32767);
-                packetContext.getPlayer().addMessage(new LiteralText(I18n.translate("text.rei.cheat_items").replaceAll("\\{item_name}", SearchArgument.tryGetItemStackName(stack.copy())).replaceAll("\\{item_count}", stack.copy().getCount() + "").replaceAll("\\{player_name}", player)), false);
+                packetContext.getPlayer().addMessage(new LiteralText(I18n.translate("text.rei.cheat_items").replaceAll("\\{item_name}", EntryStack.create(stack.copy()).asFormattedText().getString()).replaceAll("\\{item_count}", stack.copy().getCount() + "").replaceAll("\\{player_name}", player)), false);
             });
             ClientSidePacketRegistry.INSTANCE.register(RoughlyEnoughItemsNetwork.NOT_ENOUGH_ITEMS_PACKET, (packetContext, packetByteBuf) -> {
                 Screen currentScreen = MinecraftClient.getInstance().currentScreen;

+ 11 - 2
src/main/java/me/shedaniel/rei/api/EntryStack.java

@@ -32,6 +32,7 @@ import me.shedaniel.rei.api.widgets.Tooltip;
 import me.shedaniel.rei.impl.EmptyEntryStack;
 import me.shedaniel.rei.impl.FluidEntryStack;
 import me.shedaniel.rei.impl.ItemEntryStack;
+import me.shedaniel.rei.utils.CollectionUtils;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.fluid.Fluid;
@@ -40,7 +41,9 @@ import net.minecraft.item.ItemConvertible;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.CompoundTag;
 import net.minecraft.nbt.StringNbtReader;
+import net.minecraft.text.LiteralText;
 import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.registry.Registry;
@@ -54,7 +57,7 @@ import java.util.function.Function;
 import java.util.function.Supplier;
 
 @SuppressWarnings("deprecation")
-public interface EntryStack {
+public interface EntryStack extends TextRepresentable {
     
     static EntryStack empty() {
         return EmptyEntryStack.EMPTY;
@@ -137,8 +140,14 @@ public interface EntryStack {
         return copyFluidToItem(stack);
     }
     
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval
     static EntryStack copyFluidToItem(EntryStack stack) {
-        return FluidSupportProvider.INSTANCE.fluidToItem(stack);
+        Item bucketItem = stack.getFluid().getBucketItem();
+        if (bucketItem != null) {
+            return EntryStack.create(bucketItem);
+        }
+        return EntryStack.empty();
     }
     
     @Deprecated

+ 25 - 0
src/main/java/me/shedaniel/rei/api/TextRepresentable.java

@@ -0,0 +1,25 @@
+package me.shedaniel.rei.api;
+
+import me.shedaniel.math.impl.PointHelper;
+import me.shedaniel.rei.api.widgets.Tooltip;
+import net.minecraft.text.LiteralText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.jetbrains.annotations.NotNull;
+
+public interface TextRepresentable {
+    @NotNull
+    default Text asFormattedText() {
+        if (this instanceof EntryStack) {
+            Tooltip tooltip = ((EntryStack) this).getTooltip(PointHelper.ofMouse());
+            if (tooltip != null && !tooltip.getText().isEmpty())
+                return tooltip.getText().get(0);
+        }
+        return new LiteralText("");
+    }
+    
+    @NotNull
+    default Text asFormatStrippedText() {
+        return new LiteralText(Formatting.strip(asFormattedText().getString()));
+    }
+}

+ 13 - 0
src/main/java/me/shedaniel/rei/api/TextTest.java

@@ -0,0 +1,13 @@
+package me.shedaniel.rei.api;
+
+import net.minecraft.text.LiteralText;
+import net.minecraft.text.MutableText;
+import net.minecraft.util.Formatting;
+
+public class TextTest {
+    public static void main(String[] args) {
+        MutableText text = new LiteralText("adaw").append("dawdwdaw").formatted(Formatting.RED);
+        System.out.println(text.getString());
+        System.out.println(text.getString());
+    }
+}

+ 7 - 1
src/main/java/me/shedaniel/rei/api/fluid/FluidSupportProvider.java

@@ -37,13 +37,19 @@ public interface FluidSupportProvider {
     
     void registerFluidProvider(@NotNull FluidProvider provider);
     
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval
     @NotNull
-    EntryStack fluidToItem(@NotNull EntryStack fluidStack);
+    default EntryStack fluidToItem(@NotNull EntryStack fluidStack) {
+        return EntryStack.empty();
+    }
     
     @NotNull
     EntryStack itemToFluid(@NotNull EntryStack itemStack);
     
     interface FluidProvider {
+        @Deprecated
+        @ApiStatus.ScheduledForRemoval
         @NotNull
         default EntryStack fluidToItem(@NotNull EntryStack fluidStack) {
             return EntryStack.empty();

+ 2 - 2
src/main/java/me/shedaniel/rei/gui/OverlaySearchField.java

@@ -135,7 +135,7 @@ public class OverlaySearchField extends TextFieldWidget {
                 addToHistory(getText());
                 setFocused(false);
                 return true;
-            } else if (int_1 == 265) {
+            } else if (int_1 == 264) {
                 int i = history.indexOf(getText()) - 1;
                 if (i < -1 && getText().isEmpty())
                     i = history.size() - 1;
@@ -147,7 +147,7 @@ public class OverlaySearchField extends TextFieldWidget {
                     setText(history.get(i));
                     return true;
                 }
-            } else if (int_1 == 264) {
+            } else if (int_1 == 265) {
                 int i = history.indexOf(getText()) + 1;
                 if (i > 0) {
                     setText(i < history.size() ? history.get(i) : "");

+ 1 - 2
src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java

@@ -342,10 +342,9 @@ public class RecipeViewingScreen extends Screen implements RecipeScreen {
             preWidgets.add(workingStationsBaseWidget = Widgets.createCategoryBase(new Rectangle(xx - 5, yy - 5, 15 + innerWidth * 16, 10 + actualHeight * 16)));
             preWidgets.add(Widgets.createSlotBase(new Rectangle(xx - 1, yy - 1, innerWidth * 16 + 2, actualHeight * 16 + 2)));
             int index = 0;
-            List<Text> list = Collections.singletonList(new TranslatableText("text.rei.working_station").formatted(Formatting.YELLOW));
             xx += (innerWidth - 1) * 16;
             for (List<EntryStack> workingStation : workingStations) {
-                preWidgets.add(new WorkstationSlotWidget(xx, yy, CollectionUtils.map(workingStation, stack -> stack.copy().setting(EntryStack.Settings.TOOLTIP_APPEND_EXTRA, s -> list))));
+                preWidgets.add(new WorkstationSlotWidget(xx, yy, workingStation));
                 index++;
                 yy += 16;
                 if (index >= hh) {

+ 1 - 6
src/main/java/me/shedaniel/rei/gui/VillagerRecipeViewingScreen.java

@@ -41,7 +41,6 @@ import me.shedaniel.rei.gui.widget.Widget;
 import me.shedaniel.rei.impl.ClientHelperImpl;
 import me.shedaniel.rei.impl.InternalWidgets;
 import me.shedaniel.rei.impl.ScreenHelper;
-import me.shedaniel.rei.utils.CollectionUtils;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Element;
 import net.minecraft.client.gui.screen.Screen;
@@ -51,15 +50,12 @@ import net.minecraft.client.util.NarratorManager;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.LiteralText;
-import net.minecraft.text.Text;
 import net.minecraft.text.TranslatableText;
-import net.minecraft.util.Formatting;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
 import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -171,9 +167,8 @@ public class VillagerRecipeViewingScreen extends Screen implements RecipeScreen
             widgets.add(Widgets.createCategoryBase(new Rectangle(xx - 5, bounds.y + bounds.height - 5, 10 + w * 16, 12 + h * 16)));
             widgets.add(Widgets.createSlotBase(new Rectangle(xx - 1, yy - 1, 2 + w * 16, 2 + h * 16)));
             int index = 0;
-            List<Text> list = Collections.singletonList(new TranslatableText("text.rei.working_station").formatted(Formatting.YELLOW));
             for (List<EntryStack> workingStation : workingStations) {
-                widgets.add(new RecipeViewingScreen.WorkstationSlotWidget(xx, yy, CollectionUtils.map(workingStation, stack -> stack.copy().setting(EntryStack.Settings.TOOLTIP_APPEND_EXTRA, s -> list))));
+                widgets.add(new RecipeViewingScreen.WorkstationSlotWidget(xx, yy, workingStation));
                 index++;
                 xx += 16;
                 if (index >= ww) {

+ 8 - 3
src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java

@@ -46,6 +46,7 @@ import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.Tessellator;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.item.Item;
 import net.minecraft.item.ItemGroup;
 import net.minecraft.text.LiteralText;
 import net.minecraft.text.Text;
@@ -69,7 +70,7 @@ import java.util.stream.Collectors;
 public class EntryListWidget extends WidgetWithBounds {
     
     static final Supplier<Boolean> RENDER_ENCHANTMENT_GLINT = ConfigObject.getInstance()::doesRenderEntryEnchantmentGlint;
-    static final Comparator<? super EntryStack> ENTRY_NAME_COMPARER = Comparator.comparing(SearchArgument::tryGetEntryStackName);
+    static final Comparator<? super EntryStack> ENTRY_NAME_COMPARER = Comparator.comparing(stack -> stack.asFormatStrippedText().getString());
     static final Comparator<? super EntryStack> ENTRY_GROUP_COMPARER = Comparator.comparingInt(stack -> {
         if (stack.getType() == EntryStack.Type.ITEM) {
             ItemGroup group = stack.getItem().getGroup();
@@ -729,8 +730,12 @@ public class EntryListWidget extends WidgetWithBounds {
             if (containsMouse(mouseX, mouseY) && ClientHelper.getInstance().isCheating()) {
                 EntryStack entry = getCurrentEntry().copy();
                 if (!entry.isEmpty()) {
-                    if (entry.getType() == EntryStack.Type.FLUID)
-                        entry = EntryStack.copyFluidToItem(entry);
+                    if (entry.getType() == EntryStack.Type.FLUID) {
+                        Item bucketItem = entry.getFluid().getBucketItem();
+                        if (bucketItem != null) {
+                            entry = EntryStack.create(bucketItem);
+                        }
+                    }
                     if (entry.getType() == EntryStack.Type.ITEM)
                         entry.setAmount(button != 1 && !Screen.hasShiftDown() ? 1 : entry.getItemStack().getMaxCount());
                     ClientHelper.getInstance().tryCheatingEntry(entry);

+ 7 - 2
src/main/java/me/shedaniel/rei/gui/widget/FavoritesListWidget.java

@@ -41,6 +41,7 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.item.Item;
 import net.minecraft.text.TranslatableText;
 import net.minecraft.util.math.MathHelper;
 import org.jetbrains.annotations.ApiStatus;
@@ -297,8 +298,12 @@ public class FavoritesListWidget extends WidgetWithBounds {
             if (containsMouse(mouseX, mouseY) && ClientHelper.getInstance().isCheating()) {
                 EntryStack entry = getCurrentEntry().copy();
                 if (!entry.isEmpty()) {
-                    if (entry.getType() == EntryStack.Type.FLUID)
-                        entry = EntryStack.copyFluidToItem(entry);
+                    if (entry.getType() == EntryStack.Type.FLUID) {
+                        Item bucketItem = entry.getFluid().getBucketItem();
+                        if (bucketItem != null) {
+                            entry = EntryStack.create(bucketItem);
+                        }
+                    }
                     if (entry.getType() == EntryStack.Type.ITEM)
                         entry.setAmount(button != 1 && !Screen.hasShiftDown() ? 1 : entry.getItemStack().getMaxCount());
                     ClientHelper.getInstance().tryCheatingEntry(entry);

+ 121 - 119
src/main/java/me/shedaniel/rei/gui/widget/TextFieldWidget.java

@@ -34,6 +34,7 @@ import net.minecraft.client.render.Tessellator;
 import net.minecraft.client.render.VertexFormats;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.util.Tickable;
+import net.minecraft.util.Util;
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.Matrix4f;
 import org.jetbrains.annotations.ApiStatus;
@@ -55,9 +56,9 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
     public Function<String, String> stripInvalid;
     protected int focusedTicks;
     protected boolean editable;
-    protected int field_2103;
-    protected int cursorMax;
-    protected int cursorMin;
+    protected int firstCharacterIndex;
+    protected int selectionStart;
+    protected int selectionEnd;
     protected int editableColor;
     protected int notEditableColor;
     protected BiFunction<String, Integer, String> renderTextProvider;
@@ -65,9 +66,10 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
     private String text;
     private int maxLength;
     private boolean hasBorder;
-    private boolean field_2096;
+    private boolean focusUnlocked;
     private boolean focused;
     private boolean visible;
+    private boolean selecting;
     private String suggestion;
     private Consumer<String> changedListener;
     private Predicate<String> textPredicate;
@@ -76,7 +78,7 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
         this.text = "";
         this.maxLength = 32;
         this.hasBorder = true;
-        this.field_2096 = true;
+        this.focusUnlocked = true;
         this.editable = true;
         this.editableColor = 14737632;
         this.notEditableColor = 7368816;
@@ -111,7 +113,7 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
         this.changedListener = biConsumer_1;
     }
     
-    public void method_1854(BiFunction<String, Integer, String> biFunction_1) {
+    public void setRenderTextProvider(BiFunction<String, Integer, String> biFunction_1) {
         this.renderTextProvider = biFunction_1;
     }
     
@@ -133,25 +135,25 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
             }
             
             this.onChanged(string_1);
-            this.moveCursorToEnd();
+            this.setCursorToEnd();
         }
     }
     
     public String getSelectedText() {
-        int int_1 = Math.min(this.cursorMax, this.cursorMin);
-        int int_2 = Math.max(this.cursorMax, this.cursorMin);
-        return this.text.substring(int_1, int_2);
+        int i = Math.min(this.selectionStart, this.selectionEnd);
+        int j = Math.max(this.selectionStart, this.selectionEnd);
+        return this.text.substring(i, j);
     }
     
-    public void method_1890(Predicate<String> predicate_1) {
+    public void setTextPredicate(Predicate<String> predicate_1) {
         this.textPredicate = predicate_1;
     }
     
     public void addText(String string_1) {
         String string_2 = "";
         String string_3 = stripInvalid.apply(string_1);
-        int int_1 = Math.min(this.cursorMax, this.cursorMin);
-        int int_2 = Math.max(this.cursorMax, this.cursorMin);
+        int int_1 = Math.min(this.selectionStart, this.selectionEnd);
+        int int_2 = Math.max(this.selectionStart, this.selectionEnd);
         int int_3 = this.maxLength - this.text.length() - (int_1 - int_2);
         if (!this.text.isEmpty()) {
             string_2 = string_2 + this.text.substring(0, int_1);
@@ -172,8 +174,8 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
         
         if (this.textPredicate.test(string_2)) {
             this.text = string_2;
-            this.setCursor(int_1 + int_5);
-            this.method_1884(this.cursorMax);
+            this.setSelectionStart(int_1 + int_5);
+            this.setSelectionEnd(this.selectionStart);
             this.onChanged(this.text);
         }
     }
@@ -185,119 +187,111 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
         
     }
     
-    private void method_16873(int int_1) {
+    private void erase(int int_1) {
         if (Screen.hasControlDown()) {
-            this.method_1877(int_1);
+            this.eraseWords(int_1);
         } else {
-            this.method_1878(int_1);
+            this.eraseCharacters(int_1);
         }
-        
     }
     
-    public void method_1877(int int_1) {
+    public void eraseWords(int wordOffset) {
         if (!this.text.isEmpty()) {
-            if (this.cursorMin != this.cursorMax) {
+            if (this.selectionEnd != this.selectionStart) {
                 this.addText("");
             } else {
-                this.method_1878(this.method_1853(int_1) - this.cursorMax);
+                this.eraseCharacters(this.getWordSkipPosition(wordOffset) - this.selectionStart);
             }
         }
     }
     
-    public void method_1878(int int_1) {
+    public void eraseCharacters(int characterOffset) {
         if (!this.text.isEmpty()) {
-            if (this.cursorMin != this.cursorMax) {
+            if (this.selectionEnd != this.selectionStart) {
                 this.addText("");
             } else {
-                boolean boolean_1 = int_1 < 0;
-                int int_2 = boolean_1 ? this.cursorMax + int_1 : this.cursorMax;
-                int int_3 = boolean_1 ? this.cursorMax : this.cursorMax + int_1;
-                String string_1 = "";
-                if (int_2 >= 0) {
-                    string_1 = this.text.substring(0, int_2);
-                }
-                
-                if (int_3 < this.text.length()) {
-                    string_1 = string_1 + this.text.substring(int_3);
-                }
-                
-                if (this.textPredicate.test(string_1)) {
-                    this.text = string_1;
-                    if (boolean_1) {
-                        this.moveCursor(int_1, true);
+                int i = this.getMovedCursorIndex(characterOffset);
+                int j = Math.min(i, this.selectionStart);
+                int k = Math.max(i, this.selectionStart);
+                if (j != k) {
+                    String string = (new StringBuilder(this.text)).delete(j, k).toString();
+                    if (this.textPredicate.test(string)) {
+                        this.text = string;
+                        this.setCursor(j);
                     }
-                    
-                    this.onChanged(this.text);
                 }
+                this.onChanged(this.text);
             }
         }
     }
     
-    public int method_1853(int int_1) {
-        return this.method_1869(int_1, this.getCursor());
+    public int getWordSkipPosition(int int_1) {
+        return this.getWordSkipPosition(int_1, this.getCursor());
     }
     
-    public int method_1869(int int_1, int int_2) {
-        return this.method_1864(int_1, int_2, true);
+    public int getWordSkipPosition(int int_1, int int_2) {
+        return this.getWordSkipPosition(int_1, int_2, true);
     }
     
-    public int method_1864(int int_1, int int_2, boolean boolean_1) {
-        int int_3 = int_2;
-        boolean boolean_2 = int_1 < 0;
-        int int_4 = Math.abs(int_1);
+    public int getWordSkipPosition(int wordOffset, int cursorPosition, boolean skipOverSpaces) {
+        int i = cursorPosition;
+        boolean bl = wordOffset < 0;
+        int j = Math.abs(wordOffset);
         
-        for (int int_5 = 0; int_5 < int_4; ++int_5) {
-            if (!boolean_2) {
-                int int_6 = this.text.length();
-                int_3 = this.text.indexOf(32, int_3);
-                if (int_3 == -1) {
-                    int_3 = int_6;
+        for (int k = 0; k < j; ++k) {
+            if (!bl) {
+                int l = this.text.length();
+                i = this.text.indexOf(32, i);
+                if (i == -1) {
+                    i = l;
                 } else {
-                    while (boolean_1 && int_3 < int_6 && this.text.charAt(int_3) == ' ') {
-                        ++int_3;
+                    while (skipOverSpaces && i < l && this.text.charAt(i) == ' ') {
+                        ++i;
                     }
                 }
             } else {
-                while (boolean_1 && int_3 > 0 && this.text.charAt(int_3 - 1) == ' ') {
-                    --int_3;
+                while (skipOverSpaces && i > 0 && this.text.charAt(i - 1) == ' ') {
+                    --i;
                 }
                 
-                while (int_3 > 0 && this.text.charAt(int_3 - 1) != ' ') {
-                    --int_3;
+                while (i > 0 && this.text.charAt(i - 1) != ' ') {
+                    --i;
                 }
             }
         }
         
-        return int_3;
+        return i;
     }
     
-    public void moveCursor(int int_1, boolean resetSelect) {
-        this.moveCursorTo(this.cursorMax + int_1, resetSelect);
+    public void moveCursor(int int_1) {
+        this.setCursor(this.selectionStart + int_1);
     }
     
-    public void moveCursorTo(int int_1, boolean resetSelect) {
-        this.setCursor(int_1);
-        //        if (!this.field_17037) {
-        if (resetSelect) {
-            this.method_1884(this.cursorMax);
+    private int getMovedCursorIndex(int i) {
+        return Util.moveCursor(this.text, this.selectionStart, i);
+    }
+    
+    public void setCursor(int int_1) {
+        this.setSelectionStart(int_1);
+        if (!selecting) {
+            this.setSelectionEnd(this.selectionStart);
         }
-        
-        this.onChanged(this.text);
     }
     
-    public void moveCursorToHead() {
-        this.moveCursorTo(0, true);
+    public void setCursorToStart() {
+        this.setCursor(0);
     }
     
-    public void moveCursorToEnd() {
-        this.moveCursorTo(this.text.length(), true);
+    public void setCursorToEnd() {
+        this.setCursor(this.text.length());
     }
     
     public boolean keyPressed(int int_1, int int_2, int int_3) {
         if (this.isVisible() && this.isFocused()) {
+            this.selecting = Screen.hasShiftDown();
             if (Screen.isSelectAll(int_1)) {
-                this.moveCursorToEnd();
-                this.method_1884(0);
+                this.setCursorToEnd();
+                this.setSelectionEnd(0);
                 return true;
             } else if (Screen.isCopy(int_1)) {
                 minecraft.keyboard.setClipboard(this.getSelectedText());
@@ -319,7 +313,9 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
                 switch (int_1) {
                     case 259:
                         if (this.editable) {
-                            this.method_16873(-1);
+                            this.selecting = false;
+                            this.erase(-1);
+                            this.selecting = Screen.hasShiftDown();
                         }
                         
                         return true;
@@ -332,31 +328,33 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
                         return int_1 != 256;
                     case 261:
                         if (this.editable) {
-                            this.method_16873(1);
+                            this.selecting = false;
+                            this.erase(1);
+                            this.selecting = Screen.hasShiftDown();
                         }
                         
                         return true;
                     case 262:
                         if (Screen.hasControlDown()) {
-                            this.moveCursorTo(this.method_1853(1), !Screen.hasShiftDown());
+                            this.setCursor(this.getWordSkipPosition(1));
                         } else {
-                            this.moveCursor(1, !Screen.hasShiftDown());
+                            this.moveCursor(1);
                         }
                         
                         return true;
                     case 263:
                         if (Screen.hasControlDown()) {
-                            this.moveCursorTo(this.method_1853(-1), !Screen.hasShiftDown());
+                            this.setCursor(this.getWordSkipPosition(-1));
                         } else {
-                            this.moveCursor(-1, !Screen.hasShiftDown());
+                            this.moveCursor(-1);
                         }
                         
                         return true;
                     case 268:
-                        this.moveCursorToHead();
+                        this.setCursorToStart();
                         return true;
                     case 269:
-                        this.moveCursorToEnd();
+                        this.setCursorToEnd();
                         return true;
                 }
             }
@@ -368,7 +366,11 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
     @Override
     public boolean charTyped(char char_1, int int_1) {
         if (this.isVisible() && this.isFocused()) {
-            if (SharedConstants.isValidChar(char_1)) {
+            if (SharedConstants.isValidChar(char_1) && !(
+                    Screen.hasControlDown() && !Screen.hasShiftDown() && !Screen.hasAltDown() && (
+                            char_1 == 'a' || char_1 == 'c' || char_1 == 'v'
+                    )
+            )) {
                 if (this.editable) {
                     this.addText(Character.toString(char_1));
                 }
@@ -393,7 +395,7 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
             return false;
         } else {
             boolean boolean_1 = double_1 >= (double) this.bounds.x && double_1 < (double) (this.bounds.x + this.bounds.width) && double_2 >= (double) this.bounds.y && double_2 < (double) (this.bounds.y + this.bounds.height);
-            if (this.field_2096) {
+            if (this.focusUnlocked) {
                 this.setFocused(boolean_1);
             }
             
@@ -403,8 +405,8 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
                     int_2 -= 4;
                 }
                 
-                String string_1 = this.font.trimToWidth(this.text.substring(this.field_2103), this.getWidth());
-                this.moveCursorTo(this.font.trimToWidth(string_1, int_2).length() + this.field_2103, true);
+                String string_1 = this.font.trimToWidth(this.text.substring(this.firstCharacterIndex), this.getWidth());
+                this.setCursor(this.font.trimToWidth(string_1, int_2).length() + this.firstCharacterIndex);
                 return true;
             } else {
                 return false;
@@ -428,9 +430,9 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
             renderBorder(matrices);
             
             int color = this.editable ? this.editableColor : this.notEditableColor;
-            int int_4 = this.cursorMax - this.field_2103;
-            int int_5 = this.cursorMin - this.field_2103;
-            String string_1 = this.font.trimToWidth(this.text.substring(this.field_2103), this.getWidth());
+            int int_4 = this.selectionStart - this.firstCharacterIndex;
+            int int_5 = this.selectionEnd - this.firstCharacterIndex;
+            String string_1 = this.font.trimToWidth(this.text.substring(this.firstCharacterIndex), this.getWidth());
             boolean boolean_1 = int_4 >= 0 && int_4 <= string_1.length();
             boolean boolean_2 = this.focused && this.focusedTicks / 6 % 2 == 0 && boolean_1;
             int x = this.hasBorder ? this.bounds.x + 4 : this.bounds.x;
@@ -442,10 +444,10 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
             
             if (!string_1.isEmpty()) {
                 String string_2 = boolean_1 ? string_1.substring(0, int_4) : string_1;
-                int_8 = this.font.drawWithShadow(matrices, this.renderTextProvider.apply(string_2, this.field_2103), (float) x, (float) y, color);
+                int_8 = this.font.drawWithShadow(matrices, this.renderTextProvider.apply(string_2, this.firstCharacterIndex), (float) x, (float) y, color);
             }
             
-            boolean isCursorInsideText = this.cursorMax < this.text.length() || this.text.length() >= this.getMaxLength();
+            boolean isCursorInsideText = this.selectionStart < this.text.length() || this.text.length() >= this.getMaxLength();
             int int_9 = int_8;
             if (!boolean_1) {
                 int_9 = int_4 > 0 ? x + this.bounds.width : x;
@@ -455,7 +457,7 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
             int_9--;
             
             if (!string_1.isEmpty() && boolean_1 && int_4 < string_1.length()) {
-                this.font.drawWithShadow(matrices, this.renderTextProvider.apply(string_1.substring(int_4), this.cursorMax), (float) int_8, (float) y, color);
+                this.font.drawWithShadow(matrices, this.renderTextProvider.apply(string_1.substring(int_4), this.selectionStart), (float) int_8, (float) y, color);
             }
             
             if (!isCursorInsideText && text.isEmpty() && this.suggestion != null) {
@@ -537,11 +539,11 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
     }
     
     public int getCursor() {
-        return this.cursorMax;
+        return this.selectionStart;
     }
     
-    public void setCursor(int int_1) {
-        this.cursorMax = MathHelper.clamp(int_1, 0, this.text.length());
+    public void setSelectionStart(int int_1) {
+        this.selectionStart = MathHelper.clamp(int_1, 0, this.text.length());
     }
     
     public boolean hasBorder() {
@@ -587,46 +589,46 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
         return this.hasBorder() ? this.bounds.width - 8 : this.bounds.width;
     }
     
-    public void method_1884(int int_1) {
-        int int_2 = this.text.length();
-        this.cursorMin = MathHelper.clamp(int_1, 0, int_2);
+    public void setSelectionEnd(int i) {
+        int j = this.text.length();
+        this.selectionEnd = MathHelper.clamp(i, 0, j);
         if (this.font != null) {
-            if (this.field_2103 > int_2) {
-                this.field_2103 = int_2;
+            if (this.firstCharacterIndex > j) {
+                this.firstCharacterIndex = j;
             }
             
             int int_3 = this.getWidth();
-            String string_1 = this.font.trimToWidth(this.text.substring(this.field_2103), int_3);
-            int int_4 = string_1.length() + this.field_2103;
-            if (this.cursorMin == this.field_2103) {
-                this.field_2103 -= this.font.trimToWidth(this.text, int_3, true).length();
+            String string_1 = this.font.trimToWidth(this.text.substring(this.firstCharacterIndex), int_3);
+            int int_4 = string_1.length() + this.firstCharacterIndex;
+            if (this.selectionEnd == this.firstCharacterIndex) {
+                this.firstCharacterIndex -= this.font.trimToWidth(this.text, int_3, true).length();
             }
             
-            if (this.cursorMin > int_4) {
-                this.field_2103 += this.cursorMin - int_4;
-            } else if (this.cursorMin <= this.field_2103) {
-                this.field_2103 -= this.field_2103 - this.cursorMin;
+            if (this.selectionEnd > int_4) {
+                this.firstCharacterIndex += this.selectionEnd - int_4;
+            } else if (this.selectionEnd <= this.firstCharacterIndex) {
+                this.firstCharacterIndex -= this.firstCharacterIndex - this.selectionEnd;
             }
             
-            this.field_2103 = MathHelper.clamp(this.field_2103, 0, int_2);
+            this.firstCharacterIndex = MathHelper.clamp(this.firstCharacterIndex, 0, j);
         }
         
     }
     
-    public void method_1856(boolean boolean_1) {
-        this.field_2096 = boolean_1;
+    public void setFocusUnlocked(boolean boolean_1) {
+        this.focusUnlocked = boolean_1;
     }
     
     public boolean isVisible() {
         return this.visible;
     }
     
-    public void setVisible(boolean boolean_1) {
-        this.visible = boolean_1;
+    public void setVisible(boolean visible) {
+        this.visible = visible;
     }
     
-    public int method_1889(int int_1) {
-        return int_1 > this.text.length() ? this.bounds.x : this.bounds.x + this.font.getStringWidth(this.text.substring(0, int_1));
+    public int getCharacterX(int index) {
+        return index > this.text.length() ? this.bounds.x : this.bounds.x + this.font.getStringWidth(this.text.substring(0, index));
     }
     
 }

+ 19 - 23
src/main/java/me/shedaniel/rei/impl/FluidEntryStack.java

@@ -30,12 +30,14 @@ import me.shedaniel.rei.api.ClientHelper;
 import me.shedaniel.rei.api.ConfigObject;
 import me.shedaniel.rei.api.EntryStack;
 import me.shedaniel.rei.api.widgets.Tooltip;
+import me.shedaniel.rei.utils.CollectionUtils;
 import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
 import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.render.BufferBuilder;
 import net.minecraft.client.render.Tessellator;
 import net.minecraft.client.render.VertexFormats;
+import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.texture.Sprite;
 import net.minecraft.client.texture.SpriteAtlasTexture;
 import net.minecraft.client.util.math.MatrixStack;
@@ -43,12 +45,15 @@ import net.minecraft.fluid.Fluid;
 import net.minecraft.fluid.Fluids;
 import net.minecraft.text.LiteralText;
 import net.minecraft.text.Text;
+import net.minecraft.text.TranslatableText;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.Pair;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Matrix4f;
 import net.minecraft.util.registry.Registry;
+import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.HashMap;
@@ -60,7 +65,6 @@ import java.util.stream.Stream;
 
 @ApiStatus.Internal
 public class FluidEntryStack extends AbstractEntryStack {
-    private static final Map<Fluid, Pair<Sprite, Integer>> FLUID_SPRITE_CACHE = new HashMap<>();
     private static final double IGNORE_AMOUNT = -1319182373;
     private Fluid fluid;
     private double amount;
@@ -78,23 +82,6 @@ public class FluidEntryStack extends AbstractEntryStack {
         this.amount = amount;
     }
     
-    protected static Pair<Sprite, Integer> getOrLoadSprite(Fluid fluid) {
-        Pair<Sprite, Integer> possibleCached = FLUID_SPRITE_CACHE.get(fluid);
-        if (possibleCached != null)
-            return possibleCached;
-        
-        FluidRenderHandler fluidRenderHandler = FluidRenderHandlerRegistry.INSTANCE.get(fluid);
-        if (fluidRenderHandler == null)
-            return null;
-        Sprite[] sprites = fluidRenderHandler.getFluidSprites(MinecraftClient.getInstance().world, MinecraftClient.getInstance().world == null ? null : BlockPos.ORIGIN, fluid.getDefaultState());
-        int color = -1;
-        if (MinecraftClient.getInstance().world != null)
-            color = fluidRenderHandler.getFluidColor(MinecraftClient.getInstance().world, BlockPos.ORIGIN, fluid.getDefaultState());
-        Pair<Sprite, Integer> pair = new Pair<>(sprites[0], color);
-        FLUID_SPRITE_CACHE.put(fluid, pair);
-        return pair;
-    }
-    
     @Override
     public Optional<Identifier> getIdentifier() {
         return Optional.ofNullable(Registry.FLUID.getId(getFluid()));
@@ -201,7 +188,7 @@ public class FluidEntryStack extends AbstractEntryStack {
     public Tooltip getTooltip(Point point) {
         if (!get(Settings.TOOLTIP_ENABLED).get() || isEmpty())
             return null;
-        List<Text> toolTip = Lists.newArrayList(new LiteralText(SearchArgument.tryGetEntryStackName(this)));
+        List<Text> toolTip = Lists.newArrayList(asFormattedText());
         if (amount >= 0) {
             String amountTooltip = get(Settings.Fluid.AMOUNT_TOOLTIP).apply(this);
             if (amountTooltip != null)
@@ -228,10 +215,10 @@ public class FluidEntryStack extends AbstractEntryStack {
     @Override
     public void render(MatrixStack matrices, Rectangle bounds, int mouseX, int mouseY, float delta) {
         if (get(Settings.RENDER).get()) {
-            Pair<Sprite, Integer> pair = getOrLoadSprite(getFluid());
-            if (pair != null) {
-                Sprite sprite = pair.getLeft();
-                int color = pair.getRight();
+            SimpleFluidRenderer.FluidRenderingData renderingData = SimpleFluidRenderer.fromFluid(fluid);
+            if (renderingData != null) {
+                Sprite sprite = renderingData.getSprite();
+                int color = renderingData.getColor();
                 int a = 255;
                 int r = (color >> 16 & 255);
                 int g = (color >> 8 & 255);
@@ -249,4 +236,13 @@ public class FluidEntryStack extends AbstractEntryStack {
             }
         }
     }
+    
+    @NotNull
+    @Override
+    public Text asFormattedText() {
+        Identifier id = Registry.FLUID.getId(fluid);
+        if (I18n.hasTranslation("block." + id.toString().replaceFirst(":", ".")))
+            return new TranslatableText("block." + id.toString().replaceFirst(":", "."));
+        return new LiteralText(CollectionUtils.mapAndJoinToString(id.getPath().split("_"), StringUtils::capitalize, " "));
+    }
 }

+ 0 - 12
src/main/java/me/shedaniel/rei/impl/FluidSupportProviderImpl.java

@@ -46,18 +46,6 @@ public class FluidSupportProviderImpl implements FluidSupportProvider {
         providers.add(Objects.requireNonNull(provider, "Registered provider is null!"));
     }
     
-    @Override
-    public @NotNull EntryStack fluidToItem(@NotNull EntryStack fluidStack) {
-        if (fluidStack.isEmpty()) return EntryStack.empty();
-        if (fluidStack.getType() != EntryStack.Type.FLUID)
-            throw new IllegalArgumentException("EntryStack must be fluid!");
-        for (FluidProvider provider : providers) {
-            EntryStack stack = Objects.requireNonNull(provider.fluidToItem(fluidStack), provider.getClass() + " is creating null objects for fluidToItem!");
-            if (!stack.isEmpty()) return stack;
-        }
-        return EntryStack.empty();
-    }
-    
     @Override
     public @NotNull EntryStack itemToFluid(@NotNull EntryStack itemStack) {
         if (itemStack.isEmpty()) return EntryStack.empty();

+ 37 - 1
src/main/java/me/shedaniel/rei/impl/ItemEntryStack.java

@@ -30,6 +30,7 @@ import me.shedaniel.math.Rectangle;
 import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.api.widgets.Tooltip;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.item.TooltipContext;
 import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.render.OverlayTexture;
 import net.minecraft.client.render.VertexConsumerProvider;
@@ -37,16 +38,21 @@ import net.minecraft.client.render.model.BakedModel;
 import net.minecraft.client.render.model.json.ModelTransformation;
 import net.minecraft.client.texture.SpriteAtlasTexture;
 import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.CompoundTag;
 import net.minecraft.nbt.Tag;
+import net.minecraft.text.LiteralText;
 import net.minecraft.text.Text;
+import net.minecraft.text.TranslatableText;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.registry.Registry;
 import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -257,7 +263,7 @@ public class ItemEntryStack extends AbstractEntryStack implements OptimalEntrySt
     public Tooltip getTooltip(Point point) {
         if (isEmpty() || !get(Settings.TOOLTIP_ENABLED).get())
             return null;
-        List<Text> toolTip = Lists.newArrayList(SearchArgument.tryGetItemStackToolTip(getItemStack(), true));
+        List<Text> toolTip = Lists.newArrayList(tryGetItemStackToolTip(true));
         toolTip.addAll(get(Settings.TOOLTIP_APPEND_EXTRA).apply(this));
         if (get(Settings.TOOLTIP_APPEND_MOD).get() && ConfigObject.getInstance().shouldAppendModNames()) {
             final Text modString = ClientHelper.getInstance().getFormattedModFromItem(getItem());
@@ -329,4 +335,34 @@ public class ItemEntryStack extends AbstractEntryStack implements OptimalEntrySt
             MinecraftClient.getInstance().getItemRenderer().zOffset = 0.0F;
         }
     }
+    
+    private static final List<Item> SEARCH_BLACKLISTED = Lists.newArrayList();
+    
+    @Override
+    public @NotNull Text asFormattedText() {
+        if (!SEARCH_BLACKLISTED.contains(getItem()))
+            try {
+                return getItemStack().getName();
+            } catch (Throwable e) {
+                e.printStackTrace();
+                SEARCH_BLACKLISTED.add(getItem());
+            }
+        try {
+            return new TranslatableText("item." + Registry.ITEM.getId(getItem()).toString().replace(":", "."));
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+        return new LiteralText("ERROR");
+    }
+    
+    private List<Text> tryGetItemStackToolTip(boolean careAboutAdvanced) {
+        if (!SEARCH_BLACKLISTED.contains(getItem()))
+            try {
+                return itemStack.getTooltip(MinecraftClient.getInstance().player, MinecraftClient.getInstance().options.advancedItemTooltips && careAboutAdvanced ? TooltipContext.Default.ADVANCED : TooltipContext.Default.NORMAL);
+            } catch (Throwable e) {
+                e.printStackTrace();
+                SEARCH_BLACKLISTED.add(getItem());
+            }
+        return Collections.singletonList(asFormattedText());
+    }
 }

+ 1 - 1
src/main/java/me/shedaniel/rei/impl/RenderingEntry.java

@@ -30,7 +30,7 @@ import org.jetbrains.annotations.ApiStatus;
 
 import java.util.Optional;
 
-@ApiStatus.Internal
+@ApiStatus.OverrideOnly
 public abstract class RenderingEntry extends DrawableHelper implements EntryStack {
     @Override
     public Optional<Identifier> getIdentifier() {

+ 49 - 203
src/main/java/me/shedaniel/rei/impl/SearchArgument.java

@@ -23,83 +23,72 @@
 
 package me.shedaniel.rei.impl;
 
+import com.google.common.base.MoreObjects;
 import com.google.common.collect.Lists;
 import me.shedaniel.math.Point;
-import me.shedaniel.math.impl.PointHelper;
-import me.shedaniel.rei.api.ClientHelper;
 import me.shedaniel.rei.api.EntryStack;
 import me.shedaniel.rei.api.widgets.Tooltip;
+import me.shedaniel.rei.impl.search.AlwaysMatchingArgument;
+import me.shedaniel.rei.impl.search.Argument;
+import me.shedaniel.rei.impl.search.ArgumentsRegistry;
+import me.shedaniel.rei.impl.search.MatchStatus;
 import me.shedaniel.rei.utils.CollectionUtils;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.item.TooltipContext;
-import net.minecraft.client.resource.language.I18n;
-import net.minecraft.fluid.Fluid;
-import net.minecraft.item.Item;
-import net.minecraft.item.ItemStack;
-import net.minecraft.text.LiteralText;
 import net.minecraft.text.Text;
-import net.minecraft.util.Identifier;
-import net.minecraft.util.registry.Registry;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.ApiStatus;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
-import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 @ApiStatus.Internal
 public class SearchArgument {
-    
-    private static final String SPACE = " ", EMPTY = "";
-    private static final SearchArgument ALWAYS = new SearchArgument(ArgumentType.ALWAYS, "", true);
-    private static List<Item> searchBlacklisted = Lists.newArrayList();
-    private ArgumentType argumentType;
+    public static final String SPACE = " ", EMPTY = "";
+    private static final SearchArgument ALWAYS = new SearchArgument(AlwaysMatchingArgument.INSTANCE, EMPTY, true);
+    private Argument argument;
     private String text;
-    private final Function<String, Boolean> INCLUDE = s -> s.contains(text);
-    private final Function<String, Boolean> NOT_INCLUDE = s -> !s.contains(text);
-    private boolean include;
+    private Object data;
+    private boolean regular;
+    private static final Pattern SPLIT_PATTERN = Pattern.compile("(?:\"([^\"]*)\")|([^\\s]+)");
     
-    public SearchArgument(ArgumentType argumentType, String text, boolean include) {
-        this(argumentType, text, include, true);
+    public SearchArgument(Argument argument, String text, boolean regular) {
+        this(argument, text, regular, true);
     }
     
-    public SearchArgument(ArgumentType argumentType, String text, boolean include, boolean autoLowerCase) {
-        this.argumentType = argumentType;
-        this.text = autoLowerCase ? text.toLowerCase(Locale.ROOT) : text;
-        this.include = include;
+    public SearchArgument(Argument argument, String text, boolean regular, boolean lowercase) {
+        this.argument = argument;
+        this.text = lowercase ? text.toLowerCase(Locale.ROOT) : text;
+        this.regular = regular;
+        this.data = null;
     }
     
     @ApiStatus.Internal
     public static List<SearchArgument.SearchArguments> processSearchTerm(String searchTerm) {
         List<SearchArgument.SearchArguments> searchArguments = Lists.newArrayList();
-        for (String split : StringUtils.splitByWholeSeparatorPreserveAllTokens(searchTerm.toLowerCase(Locale.ROOT), "|")) {
-            String[] terms = StringUtils.split(split);
-            if (terms.length == 0)
-                searchArguments.add(SearchArgument.SearchArguments.ALWAYS);
-            else {
-                SearchArgument[] arguments = new SearchArgument[terms.length];
-                for (int i = 0; i < terms.length; i++) {
-                    String term = terms[i];
-                    if (term.startsWith("-@") || term.startsWith("@-")) {
-                        arguments[i] = new SearchArgument(SearchArgument.ArgumentType.MOD, term.substring(2), false);
-                    } else if (term.startsWith("@")) {
-                        arguments[i] = new SearchArgument(SearchArgument.ArgumentType.MOD, term.substring(1), true);
-                    } else if (term.startsWith("-$") || term.startsWith("$-")) {
-                        arguments[i] = new SearchArgument(SearchArgument.ArgumentType.TAG, term.substring(2), false);
-                    } else if (term.startsWith("$")) {
-                        arguments[i] = new SearchArgument(SearchArgument.ArgumentType.TAG, term.substring(1), true);
-                    } else if (term.startsWith("-#") || term.startsWith("#-")) {
-                        arguments[i] = new SearchArgument(SearchArgument.ArgumentType.TOOLTIP, term.substring(2), false);
-                    } else if (term.startsWith("#")) {
-                        arguments[i] = new SearchArgument(SearchArgument.ArgumentType.TOOLTIP, term.substring(1), true);
-                    } else if (term.startsWith("-")) {
-                        arguments[i] = new SearchArgument(SearchArgument.ArgumentType.TEXT, term.substring(1), false);
-                    } else {
-                        arguments[i] = new SearchArgument(SearchArgument.ArgumentType.TEXT, term, true);
+        for (String split : StringUtils.splitByWholeSeparatorPreserveAllTokens(searchTerm, "|")) {
+            Matcher terms = SPLIT_PATTERN.matcher(split);
+            List<SearchArgument> arguments = Lists.newArrayList();
+            while (terms.find()) {
+                String term = MoreObjects.firstNonNull(terms.group(1), terms.group(2));
+                for (Argument argument : ArgumentsRegistry.ARGUMENTS) {
+                    MatchStatus status = argument.matchesArgumentPrefix(term);
+                    if (status.isMatched()) {
+                        arguments.add(new SearchArgument(argument, status.getText(), !status.isInverted(), !status.shouldPreserveCasing()));
+                        break;
                     }
                 }
-                searchArguments.add(new SearchArgument.SearchArguments(arguments));
+            }
+            if (arguments.isEmpty()) {
+                searchArguments.add(SearchArgument.SearchArguments.ALWAYS);
+            } else {
+                searchArguments.add(new SearchArgument.SearchArguments(arguments.toArray(new SearchArgument[0])));
+            }
+        }
+        for (SearchArguments arguments : searchArguments) {
+            for (SearchArgument argument : arguments.getArguments()) {
+                argument.data = argument.argument.prepareSearchData(argument.getText());
             }
         }
         return searchArguments;
@@ -110,72 +99,13 @@ public class SearchArgument {
         if (searchArguments.isEmpty())
             return true;
         MinecraftClient minecraft = MinecraftClient.getInstance();
-        String mod = null;
-        String modName = null;
-        String name = null;
-        String tooltip = null;
-        String[] tags = null;
+        Object[] data = new Object[ArgumentsRegistry.ARGUMENTS.size()];
         for (SearchArgument.SearchArguments arguments : searchArguments) {
             boolean applicable = true;
             for (SearchArgument argument : arguments.getArguments()) {
-                if (argument.getArgumentType() == SearchArgument.ArgumentType.ALWAYS)
-                    return true;
-                else if (argument.getArgumentType() == SearchArgument.ArgumentType.MOD) {
-                    if (mod == null)
-                        mod = stack.getIdentifier().map(Identifier::getNamespace).orElse("").replace(SPACE, EMPTY).toLowerCase(Locale.ROOT);
-                    if (mod != null && !mod.isEmpty()) {
-                        if (argument.getFunction(!argument.isInclude()).apply(mod)) {
-                            if (modName == null)
-                                modName = ClientHelper.getInstance().getModFromModId(mod).replace(SPACE, EMPTY).toLowerCase(Locale.ROOT);
-                            if (modName == null || modName.isEmpty() || argument.getFunction(!argument.isInclude()).apply(modName)) {
-                                applicable = false;
-                                break;
-                            }
-                            break;
-                        }
-                    }
-                } else if (argument.getArgumentType() == SearchArgument.ArgumentType.TEXT) {
-                    if (name == null)
-                        name = SearchArgument.tryGetEntryStackName(stack).replace(SPACE, EMPTY).toLowerCase(Locale.ROOT);
-                    if (name != null && !name.isEmpty() && argument.getFunction(!argument.isInclude()).apply(name)) {
-                        applicable = false;
-                        break;
-                    }
-                } else if (argument.getArgumentType() == SearchArgument.ArgumentType.TOOLTIP) {
-                    if (tooltip == null)
-                        tooltip = SearchArgument.tryGetEntryStackTooltip(stack).replace(SPACE, EMPTY).toLowerCase(Locale.ROOT);
-                    if (tooltip != null && !tooltip.isEmpty() && argument.getFunction(!argument.isInclude()).apply(tooltip)) {
-                        applicable = false;
-                        break;
-                    }
-                } else if (argument.getArgumentType() == SearchArgument.ArgumentType.TAG) {
-                    if (tags == null) {
-                        if (stack.getType() == EntryStack.Type.ITEM) {
-                            Identifier[] tagsFor = minecraft.getNetworkHandler().getTagManager().items().getTagsFor(stack.getItem()).toArray(new Identifier[0]);
-                            tags = new String[tagsFor.length];
-                            for (int i = 0; i < tagsFor.length; i++)
-                                tags[i] = tagsFor[i].toString();
-                        } else if (stack.getType() == EntryStack.Type.FLUID) {
-                            Identifier[] tagsFor = minecraft.getNetworkHandler().getTagManager().fluids().getTagsFor(stack.getFluid()).toArray(new Identifier[0]);
-                            tags = new String[tagsFor.length];
-                            for (int i = 0; i < tagsFor.length; i++)
-                                tags[i] = tagsFor[i].toString();
-                        } else
-                            tags = new String[0];
-                    }
-                    if (tags != null && tags.length > 0) {
-                        boolean a = false;
-                        for (String tag : tags)
-                            if (argument.getFunction(argument.isInclude()).apply(tag))
-                                a = true;
-                        if (!a) {
-                            applicable = false;
-                            break;
-                        }
-                    } else {
-                        applicable = false;
-                        break;
-                    }
+                if (argument.getArgument().matches(data, stack, argument.getText(), argument.data) != argument.isRegular()) {
+                    applicable = false;
+                    break;
                 }
             }
             if (applicable)
@@ -184,28 +114,6 @@ public class SearchArgument {
         return false;
     }
     
-    public static String tryGetEntryStackName(EntryStack stack) {
-        if (stack.getType() == EntryStack.Type.ITEM)
-            return tryGetItemStackName(stack.getItemStack());
-        else if (stack.getType() == EntryStack.Type.FLUID)
-            return tryGetFluidName(stack.getFluid());
-        Tooltip tooltip = stack.getTooltip(PointHelper.ofMouse());
-        if (tooltip != null)
-            return tooltip.getText().isEmpty() ? "" : tooltip.getText().get(0).getString();
-        return "";
-    }
-    
-    public static String tryGetEntryStackNameNoFormatting(EntryStack stack) {
-        if (stack.getType() == EntryStack.Type.ITEM)
-            return tryGetItemStackNameNoFormatting(stack.getItemStack());
-        else if (stack.getType() == EntryStack.Type.FLUID)
-            return tryGetFluidName(stack.getFluid());
-        Tooltip tooltip = stack.getTooltip(PointHelper.ofMouse());
-        if (tooltip != null)
-            return tooltip.getText().isEmpty() ? "" : tooltip.getText().get(0).getString();
-        return "";
-    }
-    
     public static String tryGetEntryStackTooltip(EntryStack stack) {
         Tooltip tooltip = stack.getTooltip(new Point());
         if (tooltip != null)
@@ -213,83 +121,21 @@ public class SearchArgument {
         return "";
     }
     
-    public static String tryGetFluidName(Fluid fluid) {
-        Identifier id = Registry.FLUID.getId(fluid);
-        if (I18n.hasTranslation("block." + id.toString().replaceFirst(":", ".")))
-            return I18n.translate("block." + id.toString().replaceFirst(":", "."));
-        return CollectionUtils.mapAndJoinToString(id.getPath().split("_"), StringUtils::capitalize, " ");
-    }
-    
-    public static List<Text> tryGetItemStackToolTip(ItemStack itemStack, boolean careAboutAdvanced) {
-        if (!searchBlacklisted.contains(itemStack.getItem()))
-            try {
-                return itemStack.getTooltip(MinecraftClient.getInstance().player, MinecraftClient.getInstance().options.advancedItemTooltips && careAboutAdvanced ? TooltipContext.Default.ADVANCED : TooltipContext.Default.NORMAL);
-            } catch (Throwable e) {
-                e.printStackTrace();
-                searchBlacklisted.add(itemStack.getItem());
-            }
-        return Collections.singletonList(new LiteralText(tryGetItemStackName(itemStack)));
-    }
-    
-    public static String tryGetItemStackName(ItemStack stack) {
-        if (!searchBlacklisted.contains(stack.getItem()))
-            try {
-                return stack.getName().getString();
-            } catch (Throwable e) {
-                e.printStackTrace();
-                searchBlacklisted.add(stack.getItem());
-            }
-        try {
-            return I18n.translate("item." + Registry.ITEM.getId(stack.getItem()).toString().replace(":", "."));
-        } catch (Throwable e) {
-            e.printStackTrace();
-        }
-        return "ERROR";
-    }
-    
-    public static String tryGetItemStackNameNoFormatting(ItemStack stack) {
-        if (!searchBlacklisted.contains(stack.getItem()))
-            try {
-                return stack.getName().asString();
-            } catch (Throwable e) {
-                e.printStackTrace();
-                searchBlacklisted.add(stack.getItem());
-            }
-        try {
-            return I18n.translate("item." + Registry.ITEM.getId(stack.getItem()).toString().replace(":", "."));
-        } catch (Throwable e) {
-            e.printStackTrace();
-        }
-        return "ERROR";
-    }
-    
-    public Function<String, Boolean> getFunction(boolean include) {
-        return include ? INCLUDE : NOT_INCLUDE;
-    }
-    
-    public ArgumentType getArgumentType() {
-        return argumentType;
+    public Argument getArgument() {
+        return argument;
     }
     
     public String getText() {
         return text;
     }
     
-    public boolean isInclude() {
-        return include;
+    public boolean isRegular() {
+        return regular;
     }
     
     @Override
     public String toString() {
-        return String.format("Argument[%s]: name = %s, include = %b", argumentType.name(), text, include);
-    }
-    
-    public enum ArgumentType {
-        TEXT,
-        MOD,
-        TOOLTIP,
-        TAG,
-        ALWAYS
+        return String.format("Argument[%s]: name = %s, regular = %b", argument.getName(), text, regular);
     }
     
     public static class SearchArguments {

+ 62 - 0
src/main/java/me/shedaniel/rei/impl/SimpleFluidRenderer.java

@@ -0,0 +1,62 @@
+package me.shedaniel.rei.impl;
+
+import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
+import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.texture.Sprite;
+import net.minecraft.fluid.Fluid;
+import net.minecraft.util.math.BlockPos;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@ApiStatus.Internal
+public final class SimpleFluidRenderer {
+    private static final Map<Fluid, FluidRenderingData> FLUID_DATA = new HashMap<>();
+    
+    private SimpleFluidRenderer() {}
+    
+    @Nullable
+    public static FluidRenderingData fromFluid(Fluid fluid) {
+        return FLUID_DATA.computeIfAbsent(fluid, FluidRenderingDataImpl::from);
+    }
+    
+    public interface FluidRenderingData {
+        Sprite getSprite();
+        
+        int getColor();
+    }
+    
+    public static final class FluidRenderingDataImpl implements FluidRenderingData {
+        private final Sprite sprite;
+        private final int color;
+        
+        public FluidRenderingDataImpl(Sprite sprite, int color) {
+            this.sprite = sprite;
+            this.color = color;
+        }
+        
+        public static FluidRenderingData from(Fluid fluid) {
+            FluidRenderHandler fluidRenderHandler = FluidRenderHandlerRegistry.INSTANCE.get(fluid);
+            if (fluidRenderHandler == null)
+                return null;
+            Sprite[] sprites = fluidRenderHandler.getFluidSprites(MinecraftClient.getInstance().world, MinecraftClient.getInstance().world == null ? null : BlockPos.ORIGIN, fluid.getDefaultState());
+            int color = -1;
+            if (MinecraftClient.getInstance().world != null)
+                color = fluidRenderHandler.getFluidColor(MinecraftClient.getInstance().world, BlockPos.ORIGIN, fluid.getDefaultState());
+            return new FluidRenderingDataImpl(sprites[0], color);
+        }
+        
+        @Override
+        public Sprite getSprite() {
+            return sprite;
+        }
+        
+        @Override
+        public int getColor() {
+            return color;
+        }
+    }
+}

+ 27 - 0
src/main/java/me/shedaniel/rei/impl/search/AlwaysMatchingArgument.java

@@ -0,0 +1,27 @@
+package me.shedaniel.rei.impl.search;
+
+import me.shedaniel.rei.api.EntryStack;
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+public final class AlwaysMatchingArgument extends Argument {
+    public static final AlwaysMatchingArgument INSTANCE = new AlwaysMatchingArgument();
+    
+    @Override
+    public String getName() {
+        return "always";
+    }
+    
+    @Override
+    public boolean matches(Object[] data, EntryStack stack, String searchText, Object searchData) {
+        return true;
+    }
+    
+    @Override
+    public MatchStatus matchesArgumentPrefix(String text) {
+        return MatchStatus.unmatched();
+    }
+    
+    private AlwaysMatchingArgument() {
+    }
+}

+ 41 - 0
src/main/java/me/shedaniel/rei/impl/search/Argument.java

@@ -0,0 +1,41 @@
+package me.shedaniel.rei.impl.search;
+
+import me.shedaniel.rei.api.EntryStack;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+@ApiStatus.Internal
+public abstract class Argument {
+    public Argument() {
+    }
+    
+    private int dataOrdinal = -1;
+    
+    public abstract String getName();
+    
+    @Nullable
+    public String getPrefix() {
+        return null;
+    }
+    
+    public MatchStatus matchesArgumentPrefix(String text) {
+        String prefix = getPrefix();
+        if (prefix == null) return MatchStatus.unmatched();
+        if (text.startsWith("-" + prefix)) return MatchStatus.invertMatched(text.substring(1 + prefix.length()));
+        if (text.startsWith(prefix + "-")) return MatchStatus.invertMatched(text.substring(1 + prefix.length()));
+        return text.startsWith(prefix) ? MatchStatus.matched(text.substring(prefix.length())) : MatchStatus.unmatched();
+    }
+    
+    public final int getDataOrdinal() {
+        if (dataOrdinal == -1) {
+            dataOrdinal = ArgumentsRegistry.ARGUMENTS.indexOf(this);
+        }
+        return dataOrdinal;
+    }
+    
+    public abstract boolean matches(Object[] data, EntryStack stack, String searchText, Object searchData);
+    
+    public Object prepareSearchData(String searchText) {
+        return null;
+    }
+}

+ 20 - 0
src/main/java/me/shedaniel/rei/impl/search/ArgumentsRegistry.java

@@ -0,0 +1,20 @@
+package me.shedaniel.rei.impl.search;
+
+import com.google.common.collect.Lists;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.List;
+
+@ApiStatus.Internal
+public final class ArgumentsRegistry {
+    public static final List<Argument> ARGUMENTS = Lists.newArrayList();
+    
+    static {
+        ARGUMENTS.add(AlwaysMatchingArgument.INSTANCE);
+        ARGUMENTS.add(ModArgument.INSTANCE);
+        ARGUMENTS.add(TooltipArgument.INSTANCE);
+        ARGUMENTS.add(TagArgument.INSTANCE);
+        ARGUMENTS.add(RegexArgument.INSTANCE);
+        ARGUMENTS.add(TextArgument.INSTANCE);
+    }
+}

+ 63 - 0
src/main/java/me/shedaniel/rei/impl/search/MatchStatus.java

@@ -0,0 +1,63 @@
+package me.shedaniel.rei.impl.search;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+
+@ApiStatus.Internal
+public final class MatchStatus {
+    private static final MatchStatus UNMATCHED = new MatchStatus(MatchType.UNMATCHED, null, false);
+    private final MatchType type;
+    @Nullable
+    private final String text;
+    private final boolean preserveCasing;
+    
+    private MatchStatus(MatchType type, @Nullable String text, boolean preserveCasing) {
+        this.type = type;
+        this.text = text;
+        this.preserveCasing = preserveCasing;
+    }
+    
+    public static MatchStatus unmatched() {
+        return UNMATCHED;
+    }
+    
+    public static MatchStatus invertMatched(@NotNull String text) {
+        return invertMatched(text, false);
+    }
+    
+    public static MatchStatus invertMatched(@NotNull String text, boolean preserveCasing) {
+        return new MatchStatus(MatchType.INVERT_MATCHED, Objects.requireNonNull(text), preserveCasing);
+    }
+    
+    public static MatchStatus matched(@NotNull String text) {
+        return matched(text, false);
+    }
+    
+    public static MatchStatus matched(@NotNull String text, boolean preserveCasing) {
+        return new MatchStatus(MatchType.MATCHED, Objects.requireNonNull(text), preserveCasing);
+    }
+    
+    public boolean isMatched() {
+        return type != MatchType.UNMATCHED;
+    }
+    
+    public boolean isInverted() {
+        return type == MatchType.INVERT_MATCHED;
+    }
+    
+    public boolean shouldPreserveCasing() {
+        return preserveCasing;
+    }
+    
+    @Nullable
+    public String getText() {
+        return text;
+    }
+    
+    public MatchType getType() {
+        return type;
+    }
+}

+ 18 - 0
src/main/java/me/shedaniel/rei/impl/search/MatchType.java

@@ -0,0 +1,18 @@
+package me.shedaniel.rei.impl.search;
+
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+public enum MatchType {
+    INVERT_MATCHED,
+    UNMATCHED,
+    MATCHED;
+    
+    public boolean isMatched() {
+        return this != UNMATCHED;
+    }
+    
+    public boolean isInverted() {
+        return this == INVERT_MATCHED;
+    }
+}

+ 43 - 0
src/main/java/me/shedaniel/rei/impl/search/ModArgument.java

@@ -0,0 +1,43 @@
+package me.shedaniel.rei.impl.search;
+
+import me.shedaniel.rei.api.ClientHelper;
+import me.shedaniel.rei.api.EntryStack;
+import net.minecraft.util.Identifier;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Locale;
+
+@ApiStatus.Internal
+public final class ModArgument extends Argument {
+    public static final ModArgument INSTANCE = new ModArgument();
+    
+    @Override
+    public String getName() {
+        return "mod";
+    }
+    
+    @Override
+    public @Nullable String getPrefix() {
+        return "@";
+    }
+    
+    @Override
+    public boolean matches(Object[] data, EntryStack stack, String searchText, Object searchData) {
+        if (data[getDataOrdinal()] == null) {
+            data[getDataOrdinal()] = new String[]{
+                    stack.getIdentifier().map(Identifier::getNamespace).orElse("").toLowerCase(Locale.ROOT),
+                    null
+            };
+        }
+        String[] strings = (String[]) data[getDataOrdinal()];
+        if (strings[0].isEmpty() || strings[0].contains(searchText)) return true;
+        if (strings[1] == null) {
+            strings[1] = ClientHelper.getInstance().getModFromModId(strings[0]).toLowerCase(Locale.ROOT);
+        }
+        return strings[1].isEmpty() || strings[1].contains(searchText);
+    }
+    
+    private ModArgument() {
+    }
+}

+ 56 - 0
src/main/java/me/shedaniel/rei/impl/search/RegexArgument.java

@@ -0,0 +1,56 @@
+package me.shedaniel.rei.impl.search;
+
+import me.shedaniel.rei.api.EntryStack;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+@ApiStatus.Internal
+public final class RegexArgument extends Argument {
+    public static final RegexArgument INSTANCE = new RegexArgument();
+    
+    @Override
+    public String getName() {
+        return "regex";
+    }
+    
+    @Override
+    public MatchStatus matchesArgumentPrefix(String text) {
+        boolean inverted = false;
+        String matchText = text;
+        if (matchText.startsWith("-")) {
+            inverted = true;
+            matchText = matchText.substring(1);
+        }
+        if (matchText.length() >= 3 && matchText.startsWith("r/") && matchText.endsWith("/"))
+            return !inverted ? MatchStatus.matched(matchText.substring(2, matchText.length() - 1), true) : MatchStatus.invertMatched(matchText.substring(2, matchText.length() - 1), true);
+        return MatchStatus.unmatched();
+    }
+    
+    @Override
+    public Object prepareSearchData(String searchText) {
+        try {
+            return Pattern.compile(searchText);
+        } catch (PatternSyntaxException ignored) {
+            return null;
+        }
+    }
+    
+    @Override
+    public boolean matches(Object[] data, EntryStack stack, String searchText, Object searchData) {
+        Pattern pattern = (Pattern) searchData;
+        if (pattern == null) return false;
+        if (data[getDataOrdinal()] == null) {
+            String name = stack.asFormatStrippedText().getString();
+            data[getDataOrdinal()] = name;
+        }
+        Matcher matcher = pattern.matcher((String) data[getDataOrdinal()]);
+        return matcher != null && matcher.matches();
+    }
+    
+    private RegexArgument() {
+    }
+}
+

+ 51 - 0
src/main/java/me/shedaniel/rei/impl/search/TagArgument.java

@@ -0,0 +1,51 @@
+package me.shedaniel.rei.impl.search;
+
+import me.shedaniel.rei.api.EntryStack;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.util.Identifier;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+@ApiStatus.Internal
+public final class TagArgument extends Argument {
+    public static final TagArgument INSTANCE = new TagArgument();
+    private static final MinecraftClient minecraft = MinecraftClient.getInstance();
+    
+    @Override
+    public String getName() {
+        return "tag";
+    }
+    
+    @Override
+    public @Nullable String getPrefix() {
+        return "$";
+    }
+    
+    @Override
+    public boolean matches(Object[] data, EntryStack stack, String searchText, Object searchData) {
+        if (data[getDataOrdinal()] == null) {
+            if (stack.getType() == EntryStack.Type.ITEM) {
+                Identifier[] tagsFor = minecraft.getNetworkHandler().getTagManager().items().getTagsFor(stack.getItem()).toArray(new Identifier[0]);
+                data[getDataOrdinal()] = new String[tagsFor.length];
+                for (int i = 0; i < tagsFor.length; i++)
+                    ((String[]) data[getDataOrdinal()])[i] = tagsFor[i].toString();
+            } else if (stack.getType() == EntryStack.Type.FLUID) {
+                Identifier[] tagsFor = minecraft.getNetworkHandler().getTagManager().fluids().getTagsFor(stack.getFluid()).toArray(new Identifier[0]);
+                data[getDataOrdinal()] = new String[tagsFor.length];
+                for (int i = 0; i < tagsFor.length; i++)
+                    ((String[]) data[getDataOrdinal()])[i] = tagsFor[i].toString();
+            } else
+                data[getDataOrdinal()] = new String[0];
+        }
+        String[] tags = (String[]) data[getDataOrdinal()];
+        if (tags.length > 0) {
+            for (String tag : tags)
+                if (tag.isEmpty() || tag.contains(searchText))
+                    return true;
+        }
+        return false;
+    }
+    
+    private TagArgument() {
+    }
+}

+ 33 - 0
src/main/java/me/shedaniel/rei/impl/search/TextArgument.java

@@ -0,0 +1,33 @@
+package me.shedaniel.rei.impl.search;
+
+import me.shedaniel.rei.api.EntryStack;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Locale;
+
+@ApiStatus.Internal
+public final class TextArgument extends Argument {
+    public static final TextArgument INSTANCE = new TextArgument();
+    
+    @Override
+    public String getName() {
+        return "text";
+    }
+    
+    @Override
+    public @Nullable String getPrefix() {
+        return "";
+    }
+    
+    @Override
+    public boolean matches(Object[] data, EntryStack stack, String searchText, Object searchData) {
+        if (data[getDataOrdinal()] == null) {
+            data[getDataOrdinal()] = stack.asFormatStrippedText().getString().toLowerCase(Locale.ROOT);
+        }
+        return ((String) data[getDataOrdinal()]).contains(searchText);
+    }
+    
+    private TextArgument() {
+    }
+}

+ 35 - 0
src/main/java/me/shedaniel/rei/impl/search/TooltipArgument.java

@@ -0,0 +1,35 @@
+package me.shedaniel.rei.impl.search;
+
+import me.shedaniel.rei.api.EntryStack;
+import me.shedaniel.rei.impl.SearchArgument;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Locale;
+
+@ApiStatus.Internal
+public final class TooltipArgument extends Argument {
+    public static final TooltipArgument INSTANCE = new TooltipArgument();
+    
+    @Override
+    public String getName() {
+        return "tooltip";
+    }
+    
+    @Override
+    public @Nullable String getPrefix() {
+        return "#";
+    }
+    
+    @Override
+    public boolean matches(Object[] data, EntryStack stack, String searchText, Object searchData) {
+        if (data[getDataOrdinal()] == null) {
+            data[getDataOrdinal()] = SearchArgument.tryGetEntryStackTooltip(stack).toLowerCase(Locale.ROOT);
+        }
+        String tooltip = (String) data[getDataOrdinal()];
+        return tooltip.isEmpty() || tooltip.contains(searchText);
+    }
+    
+    private TooltipArgument() {
+    }
+}

+ 0 - 9
src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java

@@ -399,15 +399,6 @@ public class DefaultPlugin implements REIPluginV0 {
         recipeHelper.registerScreenClickArea(new Rectangle(78, 32, 28, 23), SmokerScreen.class, SMOKING);
         recipeHelper.registerScreenClickArea(new Rectangle(78, 32, 28, 23), BlastFurnaceScreen.class, BLASTING);
         FluidSupportProvider.INSTANCE.registerFluidProvider(new FluidSupportProvider.FluidProvider() {
-            @Override
-            public @NotNull EntryStack fluidToItem(@NotNull EntryStack fluidStack) {
-                Fluid fluid = fluidStack.getFluid();
-                Item item = fluid.getBucketItem();
-                if (item == null)
-                    return EntryStack.empty();
-                return EntryStack.create(item);
-            }
-            
             @Override
             public @NotNull EntryStack itemToFluid(@NotNull EntryStack itemStack) {
                 Item item = itemStack.getItem();