From 7bbef49785f594dfe3d6eac0cfc6ee84841aae80 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sun, 21 Mar 2021 23:29:30 +0800 Subject: Rewrite search engine and expose as API Signed-off-by: shedaniel --- .../rei/api/gui/config/entry/FilteringScreen.java | 15 +- .../shedaniel/rei/gui/ContainerScreenOverlay.java | 2 +- .../me/shedaniel/rei/gui/OverlaySearchField.java | 12 +- .../rei/gui/plugin/entry/FluidEntryDefinition.java | 21 +- .../rei/gui/plugin/entry/ItemEntryDefinition.java | 9 +- .../shedaniel/rei/gui/widget/EntryListWidget.java | 17 +- .../me/shedaniel/rei/impl/AbstractEntryStack.java | 8 +- .../me/shedaniel/rei/impl/ClientHelperImpl.java | 5 +- .../me/shedaniel/rei/impl/ConfigObjectImpl.java | 7 + .../impl/OverlaySearchFieldSyntaxHighlighter.java | 9 +- .../me/shedaniel/rei/impl/PluginManagerImpl.java | 2 + .../java/me/shedaniel/rei/impl/SearchArgument.java | 219 --------------------- .../me/shedaniel/rei/impl/TypedEntryStack.java | 13 +- .../rei/impl/entry/EmptyEntryDefinition.java | 6 +- .../impl/filtering/rules/SearchFilteringRule.java | 25 ++- .../rei/impl/search/AlternativeArgument.java | 45 +++++ .../rei/impl/search/AlwaysMatchingArgument.java | 60 ------ .../impl/search/AlwaysMatchingArgumentType.java | 60 ++++++ .../me/shedaniel/rei/impl/search/Argument.java | 189 +++++++++++++++--- .../rei/impl/search/ArgumentApplicableResult.java | 122 ++++++++++++ .../me/shedaniel/rei/impl/search/ArgumentType.java | 81 ++++++++ .../rei/impl/search/ArgumentTypesRegistry.java | 52 +++++ .../rei/impl/search/ArgumentsRegistry.java | 51 ----- .../rei/impl/search/CompoundArgument.java | 63 ++++++ .../rei/impl/search/IdentifierArgumentType.java | 93 +++++++++ .../me/shedaniel/rei/impl/search/MatchStatus.java | 122 ------------ .../me/shedaniel/rei/impl/search/ModArgument.java | 107 ---------- .../shedaniel/rei/impl/search/ModArgumentType.java | 106 ++++++++++ .../shedaniel/rei/impl/search/RegexArgument.java | 93 --------- .../rei/impl/search/RegexArgumentType.java | 94 +++++++++ .../rei/impl/search/SearchProviderImpl.java | 39 ++++ .../me/shedaniel/rei/impl/search/TagArgument.java | 103 ---------- .../shedaniel/rei/impl/search/TagArgumentType.java | 101 ++++++++++ .../me/shedaniel/rei/impl/search/TextArgument.java | 66 ------- .../rei/impl/search/TextArgumentType.java | 67 +++++++ .../shedaniel/rei/impl/search/TooltipArgument.java | 84 -------- .../rei/impl/search/TooltipArgumentType.java | 83 ++++++++ 37 files changed, 1248 insertions(+), 1003 deletions(-) delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/SearchArgument.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/AlternativeArgument.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/AlwaysMatchingArgument.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/AlwaysMatchingArgumentType.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/ArgumentApplicableResult.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/ArgumentType.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/ArgumentTypesRegistry.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/ArgumentsRegistry.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/CompoundArgument.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/IdentifierArgumentType.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/MatchStatus.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/ModArgument.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/ModArgumentType.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/RegexArgument.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/RegexArgumentType.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/SearchProviderImpl.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/TagArgument.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/TagArgumentType.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/TextArgument.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/TextArgumentType.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/TooltipArgument.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/search/TooltipArgumentType.java (limited to 'runtime/src/main/java/me') diff --git a/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringScreen.java b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringScreen.java index ac7a5e0d6..798a1c9e3 100644 --- a/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/api/gui/config/entry/FilteringScreen.java @@ -43,10 +43,13 @@ import me.shedaniel.rei.api.REIHelper; import me.shedaniel.rei.api.gui.widgets.Tooltip; import me.shedaniel.rei.api.ingredient.EntryStack; import me.shedaniel.rei.api.registry.entry.EntryRegistry; +import me.shedaniel.rei.api.search.SearchFilter; +import me.shedaniel.rei.api.search.SearchProvider; import me.shedaniel.rei.gui.ContainerScreenOverlay; import me.shedaniel.rei.gui.OverlaySearchField; import me.shedaniel.rei.gui.widget.EntryWidget; -import me.shedaniel.rei.impl.SearchArgument; +import me.shedaniel.rei.impl.search.Argument; +import me.shedaniel.rei.impl.search.CompoundArgument; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.events.GuiEventListener; @@ -103,7 +106,7 @@ public class FilteringScreen extends Screen { private Button backButton; private Rectangle selectionCache; - private List lastSearchArguments = Collections.emptyList(); + private SearchFilter lastFilter = SearchFilter.matchAll(); public FilteringScreen(FilteringEntry filteringEntry) { super(new TranslatableComponent("config.roughlyenoughitems.filteringScreen")); @@ -311,10 +314,10 @@ public class FilteringScreen extends Screen { } public void updateSearch(String searchTerm) { - lastSearchArguments = SearchArgument.processSearchTerm(searchTerm); + lastFilter = SearchProvider.getInstance().createFilter(searchTerm); Set> list = Sets.newLinkedHashSet(); EntryRegistry.getInstance().getEntryStacks().forEach(stack -> { - if (canLastSearchTermsBeAppliedTo(stack)) { + if (matches(stack)) { list.add(stack.normalize()); } }); @@ -323,8 +326,8 @@ public class FilteringScreen extends Screen { updateEntriesPosition(); } - public boolean canLastSearchTermsBeAppliedTo(EntryStack stack) { - return lastSearchArguments.isEmpty() || SearchArgument.canSearchTermsBeAppliedTo(stack, lastSearchArguments); + public boolean matches(EntryStack stack) { + return lastFilter.test(stack); } public void updateEntriesPosition() { diff --git a/runtime/src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java b/runtime/src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java index 8d1a1209a..e466e342b 100644 --- a/runtime/src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java +++ b/runtime/src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java @@ -557,7 +557,7 @@ public class ContainerScreenOverlay extends REIOverlay { AbstractContainerScreen containerScreen = (AbstractContainerScreen) Minecraft.getInstance().screen; int x = containerScreen.leftPos, y = containerScreen.topPos; for (Slot slot : containerScreen.getMenu().slots) - if (!slot.hasItem() || !ENTRY_LIST_WIDGET.canLastSearchTermsBeAppliedTo(EntryStacks.of(slot.getItem()))) + if (!slot.hasItem() || !ENTRY_LIST_WIDGET.matches(EntryStacks.of(slot.getItem()))) fillGradient(matrices, x + slot.x, y + slot.y, x + slot.x + 16, y + slot.y + 16, -601874400, -601874400); } matrices.popPose(); diff --git a/runtime/src/main/java/me/shedaniel/rei/gui/OverlaySearchField.java b/runtime/src/main/java/me/shedaniel/rei/gui/OverlaySearchField.java index 9a06211a2..3591f0ab6 100644 --- a/runtime/src/main/java/me/shedaniel/rei/gui/OverlaySearchField.java +++ b/runtime/src/main/java/me/shedaniel/rei/gui/OverlaySearchField.java @@ -35,9 +35,9 @@ import me.shedaniel.rei.api.REIHelper; import me.shedaniel.rei.api.gui.config.SyntaxHighlightingMode; import me.shedaniel.rei.impl.OverlaySearchFieldSyntaxHighlighter; import me.shedaniel.rei.impl.TextTransformations; -import me.shedaniel.rei.impl.search.Argument; -import me.shedaniel.rei.impl.search.ArgumentsRegistry; -import me.shedaniel.rei.impl.search.TextArgument; +import me.shedaniel.rei.impl.search.ArgumentType; +import me.shedaniel.rei.impl.search.ArgumentTypesRegistry; +import me.shedaniel.rei.impl.search.TextArgumentType; import me.shedaniel.rei.impl.widgets.TextFieldWidget; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; @@ -84,11 +84,11 @@ public class OverlaySearchField extends TextFieldWidget implements TextFieldWidg style = ERROR_STYLE; } if (arg > 0) { - Argument argument = ArgumentsRegistry.ARGUMENT_LIST.get((arg - 1) / 2); + ArgumentType argumentType = ArgumentTypesRegistry.ARGUMENT_TYPE_LIST.get((arg - 1) / 2); if (!isPlain) { - style = argument.getHighlightedStyle(); + style = argumentType.getHighlightedStyle(); } - if (!(argument instanceof TextArgument) && hasUnderscore && arg % 2 == 1) { + if (!(argumentType instanceof TextArgumentType) && hasUnderscore && arg % 2 == 1) { style = style.withUnderlined(true); } } else if (!isPlain) { diff --git a/runtime/src/main/java/me/shedaniel/rei/gui/plugin/entry/FluidEntryDefinition.java b/runtime/src/main/java/me/shedaniel/rei/gui/plugin/entry/FluidEntryDefinition.java index b7d6312b4..85f3e8d86 100644 --- a/runtime/src/main/java/me/shedaniel/rei/gui/plugin/entry/FluidEntryDefinition.java +++ b/runtime/src/main/java/me/shedaniel/rei/gui/plugin/entry/FluidEntryDefinition.java @@ -57,10 +57,12 @@ import net.minecraft.network.chat.TextComponent; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagCollection; import net.minecraft.world.level.material.Fluid; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -69,23 +71,24 @@ public class FluidEntryDefinition implements EntryDefinition, EntryS private final EntryRenderer renderer = new FluidEntryRenderer(); @Override - public @NotNull Class getValueType() { + public Class getValueType() { return FluidStack.class; } @Override - public @NotNull EntryType getType() { + public EntryType getType() { return VanillaEntryTypes.FLUID; } @Override - public @NotNull EntryRenderer getRenderer() { + public EntryRenderer getRenderer() { return renderer; } @Override - public @NotNull Optional getIdentifier(EntryStack entry, FluidStack value) { - return Optional.ofNullable(Registry.FLUID.getKey(value.getFluid())); + @Nullable + public ResourceLocation getIdentifier(EntryStack entry, FluidStack value) { + return Registry.FLUID.getKey(value.getFluid()); } @Override @@ -153,12 +156,12 @@ public class FluidEntryDefinition implements EntryDefinition, EntryS } @Override - public @NotNull Component asFormattedText(EntryStack entry, FluidStack value) { + public Component asFormattedText(EntryStack entry, FluidStack value) { return value.getFluid().defaultFluidState().createLegacyBlock().getBlock().getName(); } @Override - public @NotNull Collection getTagsFor(EntryStack entry, FluidStack value) { + public Collection getTagsFor(EntryStack entry, FluidStack value) { TagCollection collection = Minecraft.getInstance().getConnection().getTags().getFluids(); return collection == null ? Collections.emptyList() : collection.getMatchingTags(value.getFluid()); } diff --git a/runtime/src/main/java/me/shedaniel/rei/gui/plugin/entry/ItemEntryDefinition.java b/runtime/src/main/java/me/shedaniel/rei/gui/plugin/entry/ItemEntryDefinition.java index f3c913b72..3385ae958 100644 --- a/runtime/src/main/java/me/shedaniel/rei/gui/plugin/entry/ItemEntryDefinition.java +++ b/runtime/src/main/java/me/shedaniel/rei/gui/plugin/entry/ItemEntryDefinition.java @@ -32,8 +32,6 @@ import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceSet; import me.shedaniel.math.Point; import me.shedaniel.math.Rectangle; -import me.shedaniel.rei.api.ClientHelper; -import me.shedaniel.rei.api.config.ConfigObject; import me.shedaniel.rei.api.gui.widgets.Tooltip; import me.shedaniel.rei.api.ingredient.EntryStack; import me.shedaniel.rei.api.ingredient.entry.*; @@ -55,8 +53,6 @@ import net.minecraft.client.resources.language.I18n; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.core.Registry; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagCollection; @@ -90,8 +86,9 @@ public class ItemEntryDefinition implements EntryDefinition, EntrySer } @Override - public @NotNull Optional getIdentifier(EntryStack entry, ItemStack value) { - return Optional.ofNullable(Registry.ITEM.getKey(value.getItem())); + @Nullable + public ResourceLocation getIdentifier(EntryStack entry, ItemStack value) { + return Registry.ITEM.getKey(value.getItem()); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java b/runtime/src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java index 005f670be..242be27be 100644 --- a/runtime/src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java @@ -57,12 +57,15 @@ import me.shedaniel.rei.api.ingredient.util.EntryStacks; import me.shedaniel.rei.api.registry.entry.EntryRegistry; import me.shedaniel.rei.api.registry.screen.OverlayDecider; import me.shedaniel.rei.api.registry.screen.ScreenRegistry; +import me.shedaniel.rei.api.search.SearchFilter; +import me.shedaniel.rei.api.search.SearchProvider; import me.shedaniel.rei.api.util.CollectionUtils; import me.shedaniel.rei.api.view.Views; import me.shedaniel.rei.gui.ContainerScreenOverlay; import me.shedaniel.rei.impl.ConfigManagerImpl; import me.shedaniel.rei.impl.ConfigObjectImpl; -import me.shedaniel.rei.impl.SearchArgument; +import me.shedaniel.rei.impl.search.Argument; +import me.shedaniel.rei.impl.search.CompoundArgument; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.player.LocalPlayer; @@ -128,7 +131,7 @@ public class EntryListWidget extends WidgetWithBounds { private List entries = Collections.emptyList(); private List renders = Collections.emptyList(); private List widgets = Collections.emptyList(); - private List lastSearchArguments = Collections.emptyList(); + private SearchFilter lastFilter = SearchFilter.matchAll(); private String lastSearchTerm = null; public static int entrySize() { @@ -519,7 +522,7 @@ public class EntryListWidget extends WidgetWithBounds { Stopwatch stopwatch = Stopwatch.createStarted(); if (ignoreLastSearch || this.lastSearchTerm == null || !this.lastSearchTerm.equals(searchTerm)) { this.lastSearchTerm = searchTerm; - this.lastSearchArguments = SearchArgument.processSearchTerm(searchTerm); + this.lastFilter = SearchProvider.getInstance().createFilter(searchTerm); List> list = Lists.newArrayList(); boolean checkCraftable = ConfigManager.getInstance().isCraftableOnlyEnabled() && !ContainerScreenOverlay.getInstance().inventoryStacks.isEmpty(); IntSet workingItems = checkCraftable ? new IntOpenHashSet() : null; @@ -533,7 +536,7 @@ public class EntryListWidget extends WidgetWithBounds { completableFutures.add(CompletableFuture.supplyAsync(() -> { List> filtered = Lists.newArrayList(); for (EntryStack stack : partitionStacks) { - if (canLastSearchTermsBeAppliedTo(stack)) { + if (matches(stack)) { if (workingItems != null && !workingItems.contains(EntryStacks.hashExact(stack))) continue; filtered.add(stack.normalize()); @@ -554,7 +557,7 @@ public class EntryListWidget extends WidgetWithBounds { } } else { for (EntryStack stack : stacks) { - if (canLastSearchTermsBeAppliedTo(stack)) { + if (matches(stack)) { if (workingItems != null && !workingItems.contains(EntryStacks.hashExact(stack))) continue; list.add(stack.normalize()); @@ -580,8 +583,8 @@ public class EntryListWidget extends WidgetWithBounds { updateEntriesPosition(); } - public boolean canLastSearchTermsBeAppliedTo(EntryStack stack) { - return lastSearchArguments.isEmpty() || SearchArgument.canSearchTermsBeAppliedTo(stack, lastSearchArguments); + public boolean matches(EntryStack stack) { + return lastFilter.test(stack); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/AbstractEntryStack.java b/runtime/src/main/java/me/shedaniel/rei/impl/AbstractEntryStack.java index c065ff64e..c77193309 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/AbstractEntryStack.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/AbstractEntryStack.java @@ -36,6 +36,7 @@ import me.shedaniel.rei.api.gui.widgets.Tooltip; import me.shedaniel.rei.api.ingredient.EntryStack; import me.shedaniel.rei.api.ingredient.entry.comparison.ComparisonContext; import me.shedaniel.rei.api.ingredient.util.EntryStacks; +import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -107,13 +108,16 @@ public abstract class AbstractEntryStack extends AbstractRenderer implements @Override @Nullable public Tooltip getTooltip(Point mouse) { - Tooltip[] tooltip = {this.getDefinition().getRenderer().getTooltip(this, mouse)}; + Tooltip[] tooltip = {this.get(Settings.RENDER).apply(this).cast().getTooltip(this, mouse)}; if (tooltip[0] == null) return null; tooltip[0].getText().addAll(get(EntryStack.Settings.TOOLTIP_APPEND_EXTRA).apply(this)); tooltip[0] = get(EntryStack.Settings.TOOLTIP_PROCESSOR).apply(this, tooltip[0]); if (tooltip[0] == null) return null; if (ConfigObject.getInstance().shouldAppendModNames()) { - getIdentifier().ifPresent(location -> ClientHelper.getInstance().appendModIdToTooltips(tooltip[0].getText(), location.getNamespace())); + ResourceLocation location = getIdentifier(); + if (location != null) { + ClientHelper.getInstance().appendModIdToTooltips(tooltip[0].getText(), location.getNamespace()); + } } return tooltip[0]; } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/ClientHelperImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/ClientHelperImpl.java index e2e2460a6..a498fd2f1 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/ClientHelperImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/ClientHelperImpl.java @@ -172,9 +172,10 @@ public class ClientHelperImpl implements ClientHelper { return false; } } else { - ResourceLocation identifier = entry.getIdentifier().orElse(null); - if (identifier == null) + ResourceLocation identifier = entry.getIdentifier(); + if (identifier == null) { return false; + } String tagMessage = cheatedStack.copy().getTag() != null && !cheatedStack.copy().getTag().isEmpty() ? cheatedStack.copy().getTag().getAsString() : ""; String og = cheatedStack.getCount() == 1 ? ConfigObject.getInstance().getGiveCommand().replaceAll(" \\{count}", "") : ConfigObject.getInstance().getGiveCommand(); String madeUpCommand = og.replaceAll("\\{player_name}", Minecraft.getInstance().player.getScoreboardName()).replaceAll("\\{item_name}", identifier.getPath()).replaceAll("\\{item_identifier}", identifier.toString()).replaceAll("\\{nbt}", tagMessage).replaceAll("\\{count}", String.valueOf(cheatedStack.getCount())); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java index 9c019f19a..500713fd3 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java @@ -370,6 +370,11 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData { return advanced.search.tagSearch; } + @Override + public SearchMode getIdentifierSearchMode() { + return advanced.search.identifierSearch; + } + @Override public SearchMode getModSearchMode() { return advanced.search.modSearch; @@ -512,6 +517,8 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData { @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) private SearchMode tagSearch = SearchMode.PREFIX; @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + private SearchMode identifierSearch = SearchMode.ALWAYS; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) private SearchMode modSearch = SearchMode.PREFIX; } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/OverlaySearchFieldSyntaxHighlighter.java b/runtime/src/main/java/me/shedaniel/rei/impl/OverlaySearchFieldSyntaxHighlighter.java index c2faf9110..25e92b926 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/OverlaySearchFieldSyntaxHighlighter.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/OverlaySearchFieldSyntaxHighlighter.java @@ -24,7 +24,8 @@ package me.shedaniel.rei.impl; import me.shedaniel.rei.gui.OverlaySearchField; -import me.shedaniel.rei.impl.search.ArgumentsRegistry; +import me.shedaniel.rei.impl.search.ArgumentTypesRegistry; +import me.shedaniel.rei.impl.search.Argument; import net.minecraft.util.IntRange; import org.jetbrains.annotations.ApiStatus; @@ -44,7 +45,7 @@ public class OverlaySearchFieldSyntaxHighlighter implements Consumer { @Override public void accept(String text) { this.highlighted = new byte[text.length()]; - SearchArgument.processSearchTerm(text, new SearchArgument.ProcessedSink() { + Argument.bakeArguments(text, new Argument.ProcessedSink() { @Override public void addQuote(int index) { highlighted[index] = -2; @@ -56,9 +57,9 @@ public class OverlaySearchFieldSyntaxHighlighter implements Consumer { } @Override - public void addPart(SearchArgument argument, boolean usingGrammar, Collection grammarRanges, int index) { + public void addPart(Argument argument, boolean usingGrammar, Collection grammarRanges, int index) { if (usingGrammar) { - int argIndex = ArgumentsRegistry.ARGUMENT_LIST.indexOf(argument.getArgument()) * 2 + 1; + int argIndex = ArgumentTypesRegistry.ARGUMENT_TYPE_LIST.indexOf(argument.getArgument()) * 2 + 1; for (int i = argument.start(); i < argument.end(); i++) { highlighted[i] = (byte) argIndex; } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/PluginManagerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/PluginManagerImpl.java index 031f4b22e..e0d4f7274 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/PluginManagerImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/PluginManagerImpl.java @@ -38,6 +38,7 @@ import me.shedaniel.rei.api.util.CollectionUtils; import me.shedaniel.rei.impl.entry.ItemComparatorRegistryImpl; import me.shedaniel.rei.impl.registry.CategoryRegistryImpl; import me.shedaniel.rei.impl.registry.DisplayRegistryImpl; +import me.shedaniel.rei.impl.search.SearchProviderImpl; import me.shedaniel.rei.impl.subsets.SubsetsRegistryImpl; import me.shedaniel.rei.impl.transfer.TransferHandlerRegistryImpl; import net.fabricmc.api.EnvType; @@ -62,6 +63,7 @@ public class PluginManagerImpl implements PluginManager { private final List plugins = new ArrayList<>(); public PluginManagerImpl() { + registerReloadable(new SearchProviderImpl()); registerReloadable(new ConfigManagerImpl()); registerReloadable(new EntryTypeRegistryImpl()); registerReloadable(new ItemComparatorRegistryImpl()); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/SearchArgument.java b/runtime/src/main/java/me/shedaniel/rei/impl/SearchArgument.java deleted file mode 100644 index bb209c019..000000000 --- a/runtime/src/main/java/me/shedaniel/rei/impl/SearchArgument.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * This file is licensed under the MIT License, part of Roughly Enough Items. - * Copyright (c) 2018, 2019, 2020 shedaniel - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -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.rei.api.gui.config.SearchMode; -import me.shedaniel.rei.api.gui.widgets.Tooltip; -import me.shedaniel.rei.api.ingredient.EntryStack; -import me.shedaniel.rei.api.util.CollectionUtils; -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 net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.Minecraft; -import net.minecraft.network.chat.Component; -import net.minecraft.util.IntRange; -import net.minecraft.util.Unit; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.mutable.Mutable; -import org.apache.commons.lang3.mutable.MutableObject; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -@ApiStatus.Internal -@Environment(EnvType.CLIENT) -public class SearchArgument { - public static final String SPACE = " ", EMPTY = ""; - private static final SearchArgument ALWAYS = new SearchArgument<>(AlwaysMatchingArgument.INSTANCE, EMPTY, true, -1, -1); - private Argument argument; - private String text; - private T filterData; - private boolean regular; - private final int start; - private final int end; - private static final Pattern SPLIT_PATTERN = Pattern.compile("(?:\"([^\"]*)\")|([^\\s]+)"); - - public SearchArgument(Argument argument, String text, boolean regular, int start, int end) { - this(argument, text, regular, start, end, true); - } - - public SearchArgument(Argument argument, String text, boolean regular, int start, int end, boolean lowercase) { - this.argument = argument; - this.text = lowercase ? text.toLowerCase(Locale.ROOT) : text; - this.regular = regular; - this.filterData = null; - this.start = start; - this.end = end; - } - - public int start() { - return start; - } - - public int end() { - return end; - } - - public interface ProcessedSink { - void addQuote(int index); - - void addSplitter(int index); - - void addPart(SearchArgument argument, boolean usingGrammar, Collection grammarRanges, int index); - } - - @ApiStatus.Internal - public static List processSearchTerm(String searchTerm) { - return processSearchTerm(searchTerm, null); - } - - @ApiStatus.Internal - public static List processSearchTerm(String searchTerm, @Nullable ProcessedSink sink) { - List searchArguments = Lists.newArrayList(); - int tokenStartIndex = 0; - String[] allTokens = StringUtils.splitByWholeSeparatorPreserveAllTokens(searchTerm, "|"); - for (String token : allTokens) { - Matcher terms = SPLIT_PATTERN.matcher(token); - List> arguments = Lists.newArrayList(); - while (terms.find()) { - String term = MoreObjects.firstNonNull(terms.group(1), terms.group(2)); - for (Argument argument : ArgumentsRegistry.ARGUMENT_LIST) { - if (argument.getSearchMode() == SearchMode.NEVER) continue; - MatchStatus status = argument.matchesArgumentPrefix(term); - if (status.isMatched()) { - SearchArgument searchArgument; - if (terms.group(1) != null) { - arguments.add(searchArgument = new SearchArgument<>(argument, status.getText(), !status.isInverted(), terms.start(1) + tokenStartIndex, terms.end(1) + tokenStartIndex, !status.shouldPreserveCasing())); - if (sink != null) { - sink.addQuote(terms.start() + tokenStartIndex); - if (terms.end() - 1 + tokenStartIndex < searchTerm.length()) { - sink.addQuote(terms.end() - 1 + tokenStartIndex); - } - } - } else { - arguments.add(searchArgument = new SearchArgument<>(argument, status.getText(), !status.isInverted(), terms.start(2) + tokenStartIndex, terms.end(2) + tokenStartIndex, !status.shouldPreserveCasing())); - } - if (sink != null) { - sink.addPart(searchArgument, status.isUsingGrammar(), status.grammarRanges(), terms.start() + tokenStartIndex); - } - break; - } - } - } - if (arguments.isEmpty()) { - searchArguments.add(SearchArgument.SearchArguments.ALWAYS); - } else { - searchArguments.add(new SearchArgument.SearchArguments(arguments.toArray(new SearchArgument[0]))); - } - tokenStartIndex += 1 + token.length(); - if (sink != null && tokenStartIndex - 1 < searchTerm.length()) { - sink.addSplitter(tokenStartIndex - 1); - } - } - for (SearchArguments arguments : searchArguments) { - for (SearchArgument argument : arguments.getArguments()) { - //noinspection RedundantCast - ((SearchArgument) argument).filterData = argument.argument.prepareSearchFilter(argument.getText()); - } - } - return searchArguments; - } - - @ApiStatus.Internal - public static boolean canSearchTermsBeAppliedTo(EntryStack stack, List searchArguments) { - if (searchArguments.isEmpty()) - return true; - Minecraft minecraft = Minecraft.getInstance(); - Mutable mutable = new MutableObject<>(); - for (SearchArgument.SearchArguments arguments : searchArguments) { - boolean applicable = true; - for (SearchArgument argument : arguments.getArguments()) { - mutable.setValue(null); - if (matches(argument.getArgument(), mutable, stack, argument.getText(), argument.filterData) != argument.isRegular()) { - applicable = false; - break; - } - } - if (applicable) - return true; - } - return false; - } - - private static boolean matches(Argument argument, Mutable data, EntryStack stack, String filter, R filterData) { - return argument.matches((Mutable) data, stack, filter, (T) filterData); - } - - public static String tryGetEntryStackTooltip(EntryStack stack) { - Tooltip tooltip = stack.getTooltip(new Point()); - if (tooltip != null) - return CollectionUtils.mapAndJoinToString(tooltip.getText(), Component::getString, "\n"); - return ""; - } - - public Argument getArgument() { - return argument; - } - - public String getText() { - return text; - } - - public boolean isRegular() { - return regular; - } - - @Override - public String toString() { - return String.format("Argument[%s]: name = %s, regular = %b", argument.getName(), text, regular); - } - - public static class SearchArguments { - public static final SearchArguments ALWAYS = new SearchArguments(SearchArgument.ALWAYS); - private SearchArgument[] arguments; - - public SearchArguments(SearchArgument... arguments) { - this.arguments = arguments; - } - - public SearchArgument[] getArguments() { - return arguments; - } - - public final boolean isAlways() { - return this == ALWAYS; - } - } -} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/TypedEntryStack.java b/runtime/src/main/java/me/shedaniel/rei/impl/TypedEntryStack.java index 367d67453..5ec4909c1 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/TypedEntryStack.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/TypedEntryStack.java @@ -25,8 +25,8 @@ package me.shedaniel.rei.impl; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import me.shedaniel.rei.api.ingredient.EntryStack; -import me.shedaniel.rei.api.ingredient.entry.type.BuiltinEntryTypes; import me.shedaniel.rei.api.ingredient.entry.comparison.ComparisonContext; +import me.shedaniel.rei.api.ingredient.entry.type.BuiltinEntryTypes; import me.shedaniel.rei.api.ingredient.entry.type.EntryDefinition; import me.shedaniel.rei.api.ingredient.entry.type.EntryType; import net.minecraft.network.chat.Component; @@ -34,8 +34,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Unit; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; - -import java.util.Optional; +import org.jetbrains.annotations.Nullable; @ApiStatus.Internal public class TypedEntryStack extends AbstractEntryStack { @@ -65,7 +64,8 @@ public class TypedEntryStack extends AbstractEntryStack { } @Override - public Optional getIdentifier() { + @Nullable + public ResourceLocation getIdentifier() { return getDefinition().getIdentifier(this, value); } @@ -101,11 +101,6 @@ public class TypedEntryStack extends AbstractEntryStack { return stack; } - @Override - public boolean equals(EntryStack other, ComparisonContext context) { - return this.getDefinition().equals(value, other.getValue(), context); - } - @Override public int hash(ComparisonContext context) { return getDefinition().hash(this, value, context); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/entry/EmptyEntryDefinition.java b/runtime/src/main/java/me/shedaniel/rei/impl/entry/EmptyEntryDefinition.java index cc6253622..b0a97da14 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/entry/EmptyEntryDefinition.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/entry/EmptyEntryDefinition.java @@ -47,7 +47,6 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; import java.util.Objects; -import java.util.Optional; import java.util.function.Supplier; @ApiStatus.Internal @@ -87,8 +86,9 @@ public enum EmptyEntryDefinition implements EntryDefinition, EntrySerial } @Override - public Optional getIdentifier(EntryStack entry, Object value) { - return Optional.empty(); + @Nullable + public ResourceLocation getIdentifier(EntryStack entry, Object value) { + return null; } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/filtering/rules/SearchFilteringRule.java b/runtime/src/main/java/me/shedaniel/rei/impl/filtering/rules/SearchFilteringRule.java index 6d5dd25cf..2a08765e0 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/filtering/rules/SearchFilteringRule.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/filtering/rules/SearchFilteringRule.java @@ -27,11 +27,14 @@ import com.google.common.collect.Lists; import me.shedaniel.rei.api.gui.config.entry.FilteringEntry; import me.shedaniel.rei.api.gui.config.entry.FilteringRuleOptionsScreen; import me.shedaniel.rei.api.ingredient.EntryStack; +import me.shedaniel.rei.api.search.SearchFilter; +import me.shedaniel.rei.api.search.SearchProvider; import me.shedaniel.rei.api.util.CollectionUtils; -import me.shedaniel.rei.impl.SearchArgument; import me.shedaniel.rei.impl.filtering.AbstractFilteringRule; import me.shedaniel.rei.impl.filtering.FilteringContext; import me.shedaniel.rei.impl.filtering.FilteringResult; +import me.shedaniel.rei.impl.search.Argument; +import me.shedaniel.rei.impl.search.CompoundArgument; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.ChatFormatting; @@ -54,22 +57,20 @@ import java.util.function.Consumer; @Environment(EnvType.CLIENT) public class SearchFilteringRule extends AbstractFilteringRule { - private String filter; - private List arguments; + private SearchFilter filter; private boolean show; public SearchFilteringRule() { } - public SearchFilteringRule(String filter, List arguments, boolean show) { + public SearchFilteringRule(SearchFilter filter, boolean show) { this.filter = filter; - this.arguments = arguments; this.show = show; } @Override public CompoundTag toTag(CompoundTag tag) { - tag.putString("filter", filter); + tag.putString("filter", filter.getFilter()); tag.putBoolean("show", show); return tag; } @@ -78,7 +79,7 @@ public class SearchFilteringRule extends AbstractFilteringRule> stacks, List>>> completableFutures) { @@ -117,8 +118,7 @@ public class SearchFilteringRule extends AbstractFilteringRule { List> output = Lists.newArrayList(); for (EntryStack stack : partitionStacks) { - boolean shown = SearchArgument.canSearchTermsBeAppliedTo(stack, arguments); - if (shown) { + if (filter.test(stack)) { output.add(stack); } } @@ -150,7 +150,7 @@ public class SearchFilteringRule extends AbstractFilteringRule { widget.setMaxLength(9999); if (entry != null) widget.setValue(entry.getWidget().getValue()); - else widget.setValue(rule.filter); + else widget.setValue(rule.filter.getFilter()); })); addEmpty(entryConsumer, 10); addText(entryConsumer, new TranslatableComponent("rule.roughlyenoughitems.filtering.search.show").withStyle(ChatFormatting.GRAY)); @@ -161,8 +161,7 @@ public class SearchFilteringRule extends AbstractFilteringRule> { + static final AlternativeArgument EMPTY = new AlternativeArgument(Collections.emptyList()); + + private final List> arguments; + + public AlternativeArgument(List> arguments) { + this.arguments = arguments; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + protected List> delegate() { + return arguments; + } + + public static class Builder { + private List> arguments; + + public Builder add(Argument argument) { + if (arguments == null) { + this.arguments = new ArrayList<>(); + } + + arguments.add(argument); + return this; + } + + public AlternativeArgument build() { + if (arguments == null) return AlternativeArgument.EMPTY; + if (arguments.size() == 1) return new AlternativeArgument(Collections.singletonList(arguments.get(0))); + return new AlternativeArgument(arguments); + } + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/search/AlwaysMatchingArgument.java b/runtime/src/main/java/me/shedaniel/rei/impl/search/AlwaysMatchingArgument.java deleted file mode 100644 index 065f3a676..000000000 --- a/runtime/src/main/java/me/shedaniel/rei/impl/search/AlwaysMatchingArgument.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is licensed under the MIT License, part of Roughly Enough Items. - * Copyright (c) 2018, 2019, 2020 shedaniel - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package me.shedaniel.rei.impl.search; - -import me.shedaniel.rei.api.ingredient.EntryStack; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.util.Unit; -import org.apache.commons.lang3.mutable.Mutable; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@Environment(EnvType.CLIENT) -public final class AlwaysMatchingArgument extends Argument { - public static final AlwaysMatchingArgument INSTANCE = new AlwaysMatchingArgument(); - - @Override - public String getName() { - return "always"; - } - - @Override - public boolean matches(Mutable data, EntryStack stack, String searchText, Unit filterData) { - return true; - } - - @Override - public Unit prepareSearchFilter(String searchText) { - return Unit.INSTANCE; - } - - @Override - public MatchStatus matchesArgumentPrefix(String text) { - return MatchStatus.unmatched(); - } - - private AlwaysMatchingArgument() { - } -} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/search/AlwaysMatchingArgumentType.java b/runtime/src/main/java/me/shedaniel/rei/impl/search/AlwaysMatchingArgumentType.java new file mode 100644 index 000000000..c8048261a --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/search/AlwaysMatchingArgumentType.java @@ -0,0 +1,60 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.search; + +import me.shedaniel.rei.api.ingredient.EntryStack; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.util.Unit; +import org.apache.commons.lang3.mutable.Mutable; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +@Environment(EnvType.CLIENT) +public final class AlwaysMatchingArgumentType extends ArgumentType { + public static final AlwaysMatchingArgumentType INSTANCE = new AlwaysMatchingArgumentType(); + + @Override + public String getName() { + return "always"; + } + + @Override + public boolean matches(Mutable data, EntryStack stack, String searchText, Unit filterData) { + return true; + } + + @Override + public Unit prepareSearchFilter(String searchText) { + return Unit.INSTANCE; + } + + @Override + public ArgumentApplicableResult checkApplicable(String text) { + return ArgumentApplicableResult.notApplicable(); + } + + private AlwaysMatchingArgumentType() { + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/search/Argument.java b/runtime/src/main/java/me/shedaniel/rei/impl/search/Argument.java index dd0ca4eb8..f1359e825 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/search/Argument.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/search/Argument.java @@ -23,60 +23,189 @@ package me.shedaniel.rei.impl.search; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; +import me.shedaniel.math.Point; import me.shedaniel.rei.api.gui.config.SearchMode; +import me.shedaniel.rei.api.gui.widgets.Tooltip; import me.shedaniel.rei.api.ingredient.EntryStack; +import me.shedaniel.rei.api.util.CollectionUtils; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.Component; +import net.minecraft.util.IntRange; +import net.minecraft.util.Unit; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.mutable.Mutable; +import org.apache.commons.lang3.mutable.MutableObject; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + @ApiStatus.Internal @Environment(EnvType.CLIENT) -public abstract class Argument { - public Argument() { +public class Argument { + public static final String SPACE = " ", EMPTY = ""; + static final Argument ALWAYS = new Argument<>(AlwaysMatchingArgumentType.INSTANCE, EMPTY, true, -1, -1); + private ArgumentType argumentType; + private String text; + private T filterData; + private boolean regular; + private final int start; + private final int end; + private static final Pattern SPLIT_PATTERN = Pattern.compile("(?:\"([^\"]*)\")|([^\\s]+)"); + + public Argument(ArgumentType argumentType, String text, boolean regular, int start, int end) { + this(argumentType, text, regular, start, end, true); + } + + public Argument(ArgumentType argumentType, String text, boolean regular, int start, int end, boolean lowercase) { + this.argumentType = argumentType; + this.text = lowercase ? text.toLowerCase(Locale.ROOT) : text; + this.regular = regular; + this.filterData = null; + this.start = start; + this.end = end; } - public abstract String getName(); + public int start() { + return start; + } + + public int end() { + return end; + } - @Nullable - public String getPrefix() { - return null; + public interface ProcessedSink { + void addQuote(int index); + + void addSplitter(int index); + + void addPart(Argument argument, boolean usingGrammar, Collection grammarRanges, int index); } - public Style getHighlightedStyle() { - return Style.EMPTY; + @ApiStatus.Internal + public static List bakeArguments(String searchTerm) { + return bakeArguments(searchTerm, null); + } + + @ApiStatus.Internal + public static List bakeArguments(String searchTerm, @Nullable ProcessedSink sink) { + List compoundArguments = Lists.newArrayList(); + int tokenStartIndex = 0; + String[] allTokens = StringUtils.splitByWholeSeparatorPreserveAllTokens(searchTerm, "|"); + + for (String token : allTokens) { + Matcher terms = SPLIT_PATTERN.matcher(token); + CompoundArgument.Builder builder = CompoundArgument.builder(); + while (terms.find()) { + String term = MoreObjects.firstNonNull(terms.group(1), terms.group(2)); + AlternativeArgument.Builder alternativeBuilder = AlternativeArgument.builder(); + + for (ArgumentType type : ArgumentTypesRegistry.ARGUMENT_TYPE_LIST) { + if (type.getSearchMode() == SearchMode.NEVER) continue; + ArgumentApplicableResult result = type.checkApplicable(term); + + if (result.isApplicable()) { + int group = terms.group(1) != null ? 1 : 2; + Argument argument = new Argument<>(type, result.getText(), !result.isInverted(), + terms.start(group) + tokenStartIndex, terms.end(group) + tokenStartIndex, !result.shouldPreserveCasing()); + alternativeBuilder.add(argument); + if (sink != null) { + if (group == 1) { + sink.addQuote(terms.start() + tokenStartIndex); + if (terms.end() - 1 + tokenStartIndex < searchTerm.length()) { + sink.addQuote(terms.end() - 1 + tokenStartIndex); + } + } + sink.addPart(argument, result.isUsingGrammar(), result.grammarRanges(), terms.start() + tokenStartIndex); + } + } + } + + builder.add(alternativeBuilder); + } + compoundArguments.add(builder.build()); + tokenStartIndex += 1 + token.length(); + if (sink != null && tokenStartIndex - 1 < searchTerm.length()) { + sink.addSplitter(tokenStartIndex - 1); + } + } + for (CompoundArgument arguments : compoundArguments) { + for (AlternativeArgument alternativeArgument : arguments) { + for (Argument argument : alternativeArgument) { + //noinspection RedundantCast + ((Argument) argument).filterData = argument.argumentType.prepareSearchFilter(argument.getText()); + } + } + } + return compoundArguments; } - public SearchMode getSearchMode() { - return SearchMode.PREFIX; + @ApiStatus.Internal + public static boolean matches(EntryStack stack, List compoundArguments) { + if (compoundArguments.isEmpty()) return true; + Mutable mutable = new MutableObject<>(); + + a: + for (CompoundArgument arguments : compoundArguments) { + for (AlternativeArgument argument : arguments) { + if (!matches(stack, argument, mutable)) { + continue a; + } + } + + return true; + } + + return false; } - public MatchStatus matchesArgumentPrefix(String text) { - MatchStatus status = checkMatchPrefix(text, getPrefix()); - if (status.isMatched()) { - return status; - } else if (getSearchMode() == SearchMode.ALWAYS) { - status = checkMatchPrefix(text, ""); - if (status.isMatched()) { - status.notUsingGrammar(); + private static boolean matches(EntryStack stack, AlternativeArgument alternativeArgument, Mutable mutable) { + if (alternativeArgument.isEmpty()) return true; + + for (Argument argument : alternativeArgument) { + mutable.setValue(null); + + if (matches(argument.getArgument(), mutable, stack, argument.getText(), argument.filterData) == argument.isRegular()) { + return true; } - return status; } - return MatchStatus.unmatched(); + + return false; } - private MatchStatus checkMatchPrefix(String text, String prefix) { - if (prefix == null) return MatchStatus.unmatched(); - if (text.startsWith("-" + prefix)) return MatchStatus.invertMatched(text.substring(1 + prefix.length())).grammar(0, prefix.length() + 1); - if (text.startsWith(prefix + "-")) return MatchStatus.invertMatched(text.substring(1 + prefix.length())).grammar(0, prefix.length() + 1); - if (text.startsWith(prefix)) return MatchStatus.matched(text.substring(prefix.length())).grammar(0, prefix.length()); - return MatchStatus.unmatched(); + private static boolean matches(ArgumentType argumentType, Mutable data, EntryStack stack, String filter, R filterData) { + return argumentType.matches((Mutable) data, stack, filter, (T) filterData); } - public abstract boolean matches(Mutable data, EntryStack stack, String searchText, T filterData); + public static String tryGetEntryStackTooltip(EntryStack stack) { + Tooltip tooltip = stack.getTooltip(new Point()); + if (tooltip != null) + return CollectionUtils.mapAndJoinToString(tooltip.getText(), Component::getString, "\n"); + return ""; + } + + public ArgumentType getArgument() { + return argumentType; + } + + public String getText() { + return text; + } + + public boolean isRegular() { + return regular; + } + + @Override + public String toString() { + return String.format("Argument[%s]: name = %s, regular = %b", argumentType.getName(), text, regular); + } - public abstract T prepareSearchFilter(String searchText); } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/search/ArgumentApplicableResult.java b/runtime/src/main/java/me/shedaniel/rei/impl/search/ArgumentApplicableResult.java new file mode 100644 index 000000000..248eec5b5 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/search/ArgumentApplicableResult.java @@ -0,0 +1,122 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.search; + +import net.minecraft.util.IntRange; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@ApiStatus.Internal +public final class ArgumentApplicableResult { + private static final ArgumentApplicableResult UNMATCHED = new ArgumentApplicableResult(MatchType.UNMATCHED, null, false); + private MatchType type; + @Nullable + private final String text; + private final boolean preserveCasing; + private boolean usingGrammar = true; + private final List grammarRanges = new ArrayList<>(); + + private ArgumentApplicableResult(MatchType type, @Nullable String text, boolean preserveCasing) { + this.type = type; + this.text = text; + this.preserveCasing = preserveCasing; + } + + public static ArgumentApplicableResult notApplicable() { + return UNMATCHED; + } + + public static ArgumentApplicableResult applyInverted(@NotNull String text) { + return apply(text, false).invert(); + } + + public static ArgumentApplicableResult applyInverted(@NotNull String text, boolean preserveCasing) { + return apply(text, preserveCasing).invert(); + } + + public static ArgumentApplicableResult apply(@NotNull String text) { + return apply(text, false); + } + + public static ArgumentApplicableResult apply(@NotNull String text, boolean preserveCasing) { + return new ArgumentApplicableResult(MatchType.MATCHED, Objects.requireNonNull(text), preserveCasing); + } + + public static ArgumentApplicableResult result(@NotNull String text, boolean preserveCasing, boolean inverted) { + return new ArgumentApplicableResult(!inverted ? MatchType.MATCHED : MatchType.INVERT_MATCHED, Objects.requireNonNull(text), preserveCasing); + } + + public List grammarRanges() { + return grammarRanges; + } + + public ArgumentApplicableResult grammar(int start, int end) { + if (end - 1 >= start) { + this.grammarR