mirror of
https://github.com/GTNewHorizons/NotEnoughItems.git
synced 2026-10-25 20:32:20 +00:00
Advanced search expressions and improved recipe search in a new pattern mode: extended+ (#692)
This commit is contained in:
1 parent
c1f7489de1
commit
e5d55a9bd8
47 files changed
+1779
-321
No files matched your search
@@ -1,3 +1,26 @@
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
|
||||
generateGrammarSource {
|
||||
maxHeapSize = "128m"
|
||||
arguments += ['-package', 'codechicken.nei.search', '-visitor', '-no-listener']
|
||||
outputDirectory = file("${projectDir}/build/generated/sources/search/antlr/codechicken/nei/search/")
|
||||
}
|
||||
|
||||
compileJava.dependsOn generateGrammarSource
|
||||
sourcesJar.dependsOn generateGrammarSource
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += ["build/generated/sources/search/antlr/"]
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//version: 1707058017
|
||||
|
||||
plugins {
|
||||
id 'antlr'
|
||||
id 'com.gtnewhorizons.gtnhconvention'
|
||||
}
|
||||
+11
-1
@@ -6,5 +6,15 @@ dependencies {
|
||||
|
||||
shadowImplementation('org.apache.commons:commons-csv:1.10.0')
|
||||
|
||||
compileOnly 'maven.modrinth:forge-essentials:7.5.1'
|
||||
compileOnly('maven.modrinth:forge-essentials:7.5.1')
|
||||
|
||||
antlr('org.antlr:antlr4:4.13.2')
|
||||
shadowImplementation('org.antlr:antlr4-runtime:4.13.2')
|
||||
|
||||
testImplementation(platform('org.junit:junit-bom:5.13.4'))
|
||||
testImplementation('org.junit.jupiter:junit-jupiter')
|
||||
testImplementation("org.mockito:mockito-core:5.+")
|
||||
|
||||
testRuntimeOnly('org.junit.platform:junit-platform-launcher')
|
||||
testRuntimeOnly("org.lwjgl.lwjgl:lwjgl:2.9.3");
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Add any additional repositiroes for your dependencies here
|
||||
// Add any additional repositories for your dependencies here
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
lexer grammar SearchExpressionLexer;
|
||||
|
||||
// Antlr4 generates imports with .*
|
||||
@header {
|
||||
// CHECKSTYLE:OFF
|
||||
import codechicken.nei.SearchTokenParser;
|
||||
}
|
||||
|
||||
@members {
|
||||
private SearchTokenParser searchParser;
|
||||
|
||||
public SearchExpressionLexer(CharStream input, SearchTokenParser searchParser) {
|
||||
this(input);
|
||||
this.searchParser = searchParser;
|
||||
}
|
||||
}
|
||||
|
||||
// Lexer rules
|
||||
REGEX_LEFT : 'r'? '/' -> pushMode(REGEX) ;
|
||||
QUOTE_LEFT : '"' -> pushMode(QUOTED) ;
|
||||
DASH : '-' ;
|
||||
RECIPE_INGREDIENTS : '<&' | '<' ;
|
||||
RECIPE_RESULT : '>&' | '>' ;
|
||||
RECIPE_OTHERS : '^&' | '^' ;
|
||||
PREFIX : CLEAN_SYMBOL {searchParser.hasRedefinedPrefix(getText().charAt(0))}? ;
|
||||
OR : '|' ;
|
||||
LEFT_BRACKET : '{' ;
|
||||
RIGHT_BRACKET : '}' ;
|
||||
PLAIN_TEXT : (CLEAN_SYMBOL | ESCAPED_SYMBOL)+ {!searchParser.hasRedefinedPrefix(getText().charAt(0))}? ;
|
||||
NEWLINE_OR_TAB : [\t\r\n] -> skip ;
|
||||
SPACE : ' ' ;
|
||||
|
||||
fragment ESCAPED_SYMBOL : '\\' . ;
|
||||
fragment CLEAN_SYMBOL : ~[-<>^{}|/\\ "] ;
|
||||
|
||||
mode REGEX;
|
||||
|
||||
REGEX_CONTENT : (~[/] | '\\/')+ ;
|
||||
REGEX_RIGHT : '/' -> popMode ;
|
||||
|
||||
mode QUOTED;
|
||||
|
||||
QUOTED_CONTENT : (~["] | '\\"')+ ;
|
||||
QUOTE_RIGHT : '"' -> popMode ;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
parser grammar SearchExpressionParser;
|
||||
|
||||
// Antlr4 generates imports with .*
|
||||
@header {
|
||||
// CHECKSTYLE:OFF
|
||||
}
|
||||
|
||||
options { tokenVocab=SearchExpressionLexer; }
|
||||
|
||||
// Parser rules
|
||||
|
||||
// Should only be used for recipes. Thanks to antlr inability to properly
|
||||
// inherit grammars, this is included where it's not supposed to be
|
||||
recipeSearchExpression
|
||||
: recipeClauseExpression*
|
||||
;
|
||||
|
||||
recipeClauseExpression
|
||||
: ingredientsPrefix=RECIPE_INGREDIENTS searchExpression[0, $ingredientsPrefix.getText().length() > 1]
|
||||
| resultPrefix=RECIPE_RESULT searchExpression[1, $resultPrefix.getText().length() > 1]
|
||||
| othersPrefix=RECIPE_OTHERS searchExpression[2, $othersPrefix.getText().length() > 1]
|
||||
| searchExpression[3, false]
|
||||
;
|
||||
|
||||
// General search expression rules
|
||||
searchExpression[int type, boolean allRecipe]
|
||||
: orExpression
|
||||
;
|
||||
|
||||
orExpression
|
||||
: sequenceExpression (OR sequenceExpression)*
|
||||
;
|
||||
|
||||
sequenceExpression
|
||||
: (SPACE* unaryExpression SPACE*)+
|
||||
;
|
||||
|
||||
unaryExpression
|
||||
: LEFT_BRACKET orExpression RIGHT_BRACKET?
|
||||
| prefixedExpression
|
||||
| negateExpression
|
||||
| token['\0']
|
||||
;
|
||||
|
||||
negateExpression
|
||||
: DASH unaryExpression
|
||||
;
|
||||
|
||||
prefixedExpression
|
||||
locals [
|
||||
Character prefix
|
||||
]
|
||||
: prefixToken=PREFIX { $prefix = $prefixToken.getText().charAt(0); } token[$prefix]
|
||||
;
|
||||
|
||||
token[Character prefix]
|
||||
: regex[prefix]
|
||||
| quoted[prefix]
|
||||
| PLAIN_TEXT
|
||||
;
|
||||
|
||||
regex[Character prefix]
|
||||
: REGEX_LEFT REGEX_CONTENT REGEX_RIGHT?
|
||||
;
|
||||
|
||||
quoted[Character prefix]
|
||||
: QUOTE_LEFT QUOTED_CONTENT QUOTE_RIGHT?
|
||||
;
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import codechicken.nei.ItemList.AnyMultiItemFilter;
|
||||
import codechicken.nei.ItemList.EverythingItemFilter;
|
||||
import codechicken.nei.ItemList.NothingItemFilter;
|
||||
import codechicken.nei.PresetsList.Preset;
|
||||
import codechicken.nei.PresetsList.PresetMode;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.filter.AnyMultiItemFilter;
|
||||
import codechicken.nei.filter.EverythingItemFilter;
|
||||
import codechicken.nei.filter.NothingItemFilter;
|
||||
import codechicken.nei.util.ItemStackFilterParser;
|
||||
|
||||
public class CollapsibleItems {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package codechicken.nei;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
|
||||
public class DoubleFilteredFilter implements ItemFilter {
|
||||
|
||||
private ItemFilter filter;
|
||||
private ItemList.PatternItemFilter patternFilter;
|
||||
|
||||
public DoubleFilteredFilter(ItemFilter filter, String itemFilter) {
|
||||
this.filter = filter;
|
||||
this.patternFilter = new ItemList.PatternItemFilter(SearchField.getPattern(itemFilter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
if (filter.matches(item)) {
|
||||
return patternFilter.matches(item);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -70,8 +70,6 @@ public class ItemHistoryPanel extends AbstractSubpanel<ItemsGrid<ItemsGridSlot,
|
||||
final int useRows = NEIClientConfig.getIntSetting("inventory.history.useRows");
|
||||
final int rows = (int) Math.min(Math.ceil(this.grid.size() * 1f / columns), useRows);
|
||||
|
||||
System.out.println("rows: " + rows + " " + columns + " " + this.grid.size() + " " + width);
|
||||
|
||||
this.w = width;
|
||||
this.h = 8 + ItemsGrid.SLOT_SIZE * Math.max(rows, 1);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package codechicken.nei;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -11,14 +10,12 @@ import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinWorkerThread;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
import net.minecraft.util.IIcon;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
@@ -28,6 +25,7 @@ import codechicken.nei.ThreadOperationTimer.TimeoutException;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.api.ItemFilter.ItemFilterProvider;
|
||||
import codechicken.nei.api.ItemInfo;
|
||||
import codechicken.nei.filter.AllMultiItemFilter;
|
||||
import codechicken.nei.recipe.InformationHandler;
|
||||
import codechicken.nei.search.TooltipFilter;
|
||||
|
||||
@@ -55,117 +53,6 @@ public class ItemList {
|
||||
*/
|
||||
public static boolean loadFinished;
|
||||
|
||||
public static class EverythingItemFilter implements ItemFilter {
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NothingItemFilter implements ItemFilter {
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NegatedItemFilter implements ItemFilter {
|
||||
|
||||
public ItemFilter filter;
|
||||
|
||||
public NegatedItemFilter(ItemFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
return this.filter == null || !this.filter.matches(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PatternItemFilter implements ItemFilter {
|
||||
|
||||
public Pattern pattern;
|
||||
|
||||
public PatternItemFilter(Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
String displayName = EnumChatFormatting.getTextWithoutFormattingCodes(item.getDisplayName());
|
||||
|
||||
if (displayName != null && !displayName.isEmpty() && pattern.matcher(displayName).find()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.hasDisplayName()) {
|
||||
displayName = EnumChatFormatting
|
||||
.getTextWithoutFormattingCodes(item.getItem().getItemStackDisplayName(item));
|
||||
|
||||
return displayName != null && !displayName.isEmpty() && pattern.matcher(displayName).find();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AllMultiItemFilter implements ItemFilter {
|
||||
|
||||
public List<ItemFilter> filters;
|
||||
|
||||
public AllMultiItemFilter(List<ItemFilter> filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
public AllMultiItemFilter(ItemFilter... filters) {
|
||||
this(new LinkedList<>(Arrays.asList(filters)));
|
||||
}
|
||||
|
||||
public AllMultiItemFilter() {
|
||||
this(new LinkedList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
for (ItemFilter filter : filters) try {
|
||||
if (filter != null && !filter.matches(item)) return false;
|
||||
} catch (Exception e) {
|
||||
NEIClientConfig.logger
|
||||
.error("Exception filtering " + item + " with " + filter + " (" + e.getMessage() + ")", e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AnyMultiItemFilter implements ItemFilter {
|
||||
|
||||
public List<ItemFilter> filters;
|
||||
|
||||
public AnyMultiItemFilter(List<ItemFilter> filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
public AnyMultiItemFilter() {
|
||||
this(new LinkedList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
for (ItemFilter filter : filters) try {
|
||||
if (filter != null && filter.matches(item)) return true;
|
||||
} catch (Exception e) {
|
||||
NEIClientConfig.logger
|
||||
.error("Exception filtering " + item + " with " + filter + " (" + e.getMessage() + ")", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static interface ItemsLoadedCallback {
|
||||
|
||||
public void itemsLoaded();
|
||||
|
||||
@@ -500,12 +500,15 @@ public class NEIClientConfig {
|
||||
API.addOption(new OptionCycled("inventory.search.widgetAutofocus", 3, true));
|
||||
|
||||
tag.getTag("inventory.search.patternMode").setComment("Search Mode").getIntValue(1);
|
||||
API.addOption(new OptionCycled("inventory.search.patternMode", 3, true) {
|
||||
API.addOption(new OptionCycled("inventory.search.patternMode", 4, true) {
|
||||
|
||||
@Override
|
||||
public boolean onClick(int button) {
|
||||
if (!super.onClick(button)) {
|
||||
return false;
|
||||
}
|
||||
SearchField.searchParser.clearCache();
|
||||
return super.onClick(button);
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -575,8 +578,11 @@ public class NEIClientConfig {
|
||||
|
||||
@Override
|
||||
public boolean onClick(int button) {
|
||||
if (!super.onClick(button)) {
|
||||
return false;
|
||||
}
|
||||
SearchField.searchParser.clearCache();
|
||||
return super.onClick(button);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -600,8 +606,11 @@ public class NEIClientConfig {
|
||||
|
||||
@Override
|
||||
public boolean onClick(int button) {
|
||||
if (!super.onClick(button)) {
|
||||
return false;
|
||||
}
|
||||
SearchField.searchParser.clearCache();
|
||||
return super.onClick(button);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -624,8 +633,11 @@ public class NEIClientConfig {
|
||||
|
||||
@Override
|
||||
public boolean onClick(int button) {
|
||||
if (!super.onClick(button)) {
|
||||
return false;
|
||||
}
|
||||
SearchField.searchParser.clearCache();
|
||||
return super.onClick(button);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -648,8 +660,11 @@ public class NEIClientConfig {
|
||||
|
||||
@Override
|
||||
public boolean onClick(int button) {
|
||||
if (!super.onClick(button)) {
|
||||
return false;
|
||||
}
|
||||
SearchField.searchParser.clearCache();
|
||||
return super.onClick(button);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -672,8 +687,11 @@ public class NEIClientConfig {
|
||||
|
||||
@Override
|
||||
public boolean onClick(int button) {
|
||||
if (!super.onClick(button)) {
|
||||
return false;
|
||||
}
|
||||
SearchField.searchParser.clearCache();
|
||||
return super.onClick(button);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -691,6 +709,9 @@ public class NEIClientConfig {
|
||||
|
||||
});
|
||||
|
||||
tag.getTag("inventory.search.logSearchExceptions").setComment("Search exceptions for extended+")
|
||||
.getBooleanValue(false);
|
||||
|
||||
String prefixRedefinitions = tag.getTag("inventory.search.prefixRedefinitions").setComment(
|
||||
"Redefine search prefixes by providing a char-to-char map (JSON). The keys are the original prefixes, the values the new ones. Example: { \"$\": \"€\", \"#\": \"+\", \"@\": \"°\", \"%\": \"!\" }")
|
||||
.getValue("{}");
|
||||
@@ -701,6 +722,7 @@ public class NEIClientConfig {
|
||||
};
|
||||
Map<Character, Character> charMap = new Gson().fromJson(prefixRedefinitions, typeToken.getType());
|
||||
SearchField.searchParser.prefixRedefinitions.putAll(charMap);
|
||||
SearchField.searchParser.updateRedefinedPrefixes();
|
||||
} catch (JsonParseException e) {
|
||||
logger.warn("Failed to convert prefix redefinitions from JSON to CharToCharMap:", e);
|
||||
}
|
||||
|
||||
@@ -68,11 +68,11 @@ public class PresetsList {
|
||||
}
|
||||
}
|
||||
|
||||
protected static class RecipesFilter implements IRecipeFilterProvider, IRecipeFilter {
|
||||
protected static class PresetRecipeFilter implements IRecipeFilterProvider, IRecipeFilter {
|
||||
|
||||
public Set<String> cache;
|
||||
|
||||
public IRecipeFilter getFilter() {
|
||||
public IRecipeFilter getRecipeFilter() {
|
||||
|
||||
if (cache == null) {
|
||||
cache = PresetsList.presets.stream().filter(p -> p.enabled && p.mode == PresetMode.REMOVE)
|
||||
@@ -145,7 +145,7 @@ public class PresetsList {
|
||||
}
|
||||
|
||||
public static final List<Preset> presets = new ArrayList<>();
|
||||
protected static RecipesFilter recipeFilter = new RecipesFilter();
|
||||
protected static PresetRecipeFilter recipeFilter = new PresetRecipeFilter();
|
||||
protected static ItemPanelFilter itemFilter = new ItemPanelFilter();
|
||||
|
||||
static {
|
||||
|
||||
@@ -3,10 +3,13 @@ package codechicken.nei;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import codechicken.nei.SearchField.GuiSearchField;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.IRecipeFilter.IRecipeFilterProvider;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.api.ItemFilter.ItemFilterProvider;
|
||||
import codechicken.nei.util.TextHistory;
|
||||
|
||||
public abstract class RecipeSearchField extends TextField {
|
||||
public abstract class RecipeSearchField extends TextField implements ItemFilterProvider, IRecipeFilterProvider {
|
||||
|
||||
private static final TextHistory history = new TextHistory();
|
||||
|
||||
@@ -58,10 +61,16 @@ public abstract class RecipeSearchField extends TextField {
|
||||
return EnumChatFormatting.getTextWithoutFormattingCodes(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter getFilter() {
|
||||
return ((GuiSearchField) field).getFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRecipeFilter getRecipeFilter() {
|
||||
return ((GuiSearchField) field).getRecipeFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFocus(boolean focus) {
|
||||
final boolean previousFocus = field.isFocused();
|
||||
|
||||
@@ -13,11 +13,13 @@ import net.minecraft.util.EnumChatFormatting;
|
||||
import net.minecraftforge.fluids.FluidStack;
|
||||
|
||||
import codechicken.lib.gui.GuiDraw;
|
||||
import codechicken.nei.ItemList.NothingItemFilter;
|
||||
import codechicken.nei.SearchTokenParser.ISearchParserProvider;
|
||||
import codechicken.nei.SearchTokenParser.SearchMode;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.IRecipeFilter.IRecipeFilterProvider;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.api.ItemFilter.ItemFilterProvider;
|
||||
import codechicken.nei.filter.NothingItemFilter;
|
||||
import codechicken.nei.recipe.StackInfo;
|
||||
import codechicken.nei.util.TextHistory;
|
||||
|
||||
@@ -88,7 +90,7 @@ public class SearchField extends TextField implements ItemFilterProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public static class GuiSearchField extends FormattedTextField {
|
||||
public static class GuiSearchField extends FormattedTextField implements ItemFilterProvider, IRecipeFilterProvider {
|
||||
|
||||
protected final SearchTokenParser searchParser;
|
||||
|
||||
@@ -104,14 +106,24 @@ public class SearchField extends TextField implements ItemFilterProvider {
|
||||
setPlaceholder(NEIClientUtils.translate("inventory.search.placeholder"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter getFilter() {
|
||||
return getFilter(getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRecipeFilter getRecipeFilter() {
|
||||
return getRecipeFilter(getText());
|
||||
}
|
||||
|
||||
public ItemFilter getFilter(String filterText) {
|
||||
return this.searchParser.getFilter(filterText);
|
||||
}
|
||||
|
||||
public IRecipeFilter getRecipeFilter(String filterText) {
|
||||
return this.searchParser.getRecipeFilter(filterText);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@@ -272,7 +284,7 @@ public class SearchField extends TextField implements ItemFilterProvider {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// regex and extended+
|
||||
if (!search.isEmpty()) {
|
||||
try {
|
||||
return Pattern.compile(search, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
|
||||
@@ -305,6 +317,10 @@ public class SearchField extends TextField implements ItemFilterProvider {
|
||||
case 2:
|
||||
text = text.replaceAll("[{}()\\[\\].+*?^$\\\\|]", "\\\\$0");
|
||||
break;
|
||||
case 3:
|
||||
text = text.replaceAll("\"", "\\\\$0");
|
||||
text = "\"" + text + "\"";
|
||||
return text;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import codechicken.nei.FormattedTextField.TextFormatter;
|
||||
import codechicken.nei.SearchTokenParser.SearchToken;
|
||||
import codechicken.nei.search.SearchExpressionFormatVisitor;
|
||||
import codechicken.nei.search.SearchExpressionUtils;
|
||||
|
||||
public class SearchTextFormatter implements TextFormatter {
|
||||
|
||||
@@ -17,50 +19,60 @@ public class SearchTextFormatter implements TextFormatter {
|
||||
}
|
||||
|
||||
public String format(String text) {
|
||||
final String[] parts = (text + "| ").split("\\|");
|
||||
StringJoiner formattedText = new StringJoiner(EnumChatFormatting.GRAY + "|");
|
||||
final int patternMode = NEIClientConfig.getIntSetting("inventory.search.patternMode");
|
||||
if (patternMode == 3) {
|
||||
final int spaceModeEnabled = NEIClientConfig.getIntSetting("inventory.search.spaceMode");
|
||||
if (spaceModeEnabled == 1) {
|
||||
text = SearchTokenParser.SPACE_PATTERN.matcher(text).replaceAll("\\\\ ");
|
||||
}
|
||||
final SearchExpressionFormatVisitor visitor = new SearchExpressionFormatVisitor(searchParser);
|
||||
return SearchExpressionUtils.visitSearchExpression(text, visitor);
|
||||
} else {
|
||||
final String[] parts = (text + "| ").split("\\|");
|
||||
StringJoiner formattedText = new StringJoiner(SearchExpressionUtils.HIGHLIGHTS.OR + "|");
|
||||
|
||||
for (int i = 0; i < parts.length - 1; i++) {
|
||||
final String filterText = parts[i];
|
||||
final List<SearchToken> tokens = searchParser.splitSearchText(filterText);
|
||||
StringBuilder formattedPart = new StringBuilder();
|
||||
int startIndex = 0;
|
||||
for (int i = 0; i < parts.length - 1; i++) {
|
||||
final String filterText = parts[i];
|
||||
final List<SearchToken> tokens = searchParser.splitSearchText(filterText);
|
||||
StringBuilder formattedPart = new StringBuilder();
|
||||
int startIndex = 0;
|
||||
|
||||
for (SearchToken token : tokens) {
|
||||
formattedPart.append(filterText.substring(startIndex, token.start));
|
||||
EnumChatFormatting tokenColor = EnumChatFormatting.RESET;
|
||||
for (SearchToken token : tokens) {
|
||||
formattedPart.append(filterText.substring(startIndex, token.start));
|
||||
EnumChatFormatting tokenColor = EnumChatFormatting.RESET;
|
||||
|
||||
if (token.firstChar != null) {
|
||||
tokenColor = searchParser.getProvider(token.firstChar).getHighlightedColor();
|
||||
if (token.firstChar != null) {
|
||||
tokenColor = searchParser.getProvider(token.firstChar).getHighlightedColor();
|
||||
}
|
||||
|
||||
if (token.ignore) {
|
||||
formattedPart.append(SearchExpressionUtils.HIGHLIGHTS.NEGATE + "-");
|
||||
}
|
||||
|
||||
if (token.firstChar != null) {
|
||||
formattedPart.append(tokenColor + String.valueOf(token.firstChar));
|
||||
}
|
||||
|
||||
if (token.quotes) {
|
||||
formattedPart.append(SearchExpressionUtils.HIGHLIGHTS.QUOTED + "\"");
|
||||
}
|
||||
|
||||
if (!token.rawText.isEmpty()) {
|
||||
formattedPart.append(tokenColor + token.rawText);
|
||||
}
|
||||
|
||||
if (token.quotes) {
|
||||
formattedPart.append(SearchExpressionUtils.HIGHLIGHTS.QUOTED + "\"");
|
||||
}
|
||||
|
||||
startIndex = token.end;
|
||||
}
|
||||
|
||||
if (token.ignore) {
|
||||
formattedPart.append(EnumChatFormatting.BLUE + "-");
|
||||
}
|
||||
|
||||
if (token.firstChar != null) {
|
||||
formattedPart.append(tokenColor + String.valueOf(token.firstChar));
|
||||
}
|
||||
|
||||
if (token.quotes) {
|
||||
formattedPart.append(EnumChatFormatting.GOLD + "\"");
|
||||
}
|
||||
|
||||
if (!token.rawText.isEmpty()) {
|
||||
formattedPart.append(tokenColor + token.rawText);
|
||||
}
|
||||
|
||||
if (token.quotes) {
|
||||
formattedPart.append(EnumChatFormatting.GOLD + "\"");
|
||||
}
|
||||
|
||||
startIndex = token.end;
|
||||
formattedPart.append(filterText.substring(startIndex, filterText.length()));
|
||||
formattedText.add(formattedPart);
|
||||
}
|
||||
|
||||
formattedPart.append(filterText.substring(startIndex, filterText.length()));
|
||||
formattedText.add(formattedPart);
|
||||
return formattedText.toString();
|
||||
}
|
||||
|
||||
return formattedText.toString();
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,10 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -15,12 +17,17 @@ import net.minecraft.client.resources.Language;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import codechicken.nei.ItemList.AllMultiItemFilter;
|
||||
import codechicken.nei.ItemList.AnyMultiItemFilter;
|
||||
import codechicken.nei.ItemList.EverythingItemFilter;
|
||||
import codechicken.nei.ItemList.NegatedItemFilter;
|
||||
import codechicken.nei.ItemList.NothingItemFilter;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.filter.AllMultiItemFilter;
|
||||
import codechicken.nei.filter.AnyItemRecipeFilter;
|
||||
import codechicken.nei.filter.AnyMultiItemFilter;
|
||||
import codechicken.nei.filter.EverythingItemFilter;
|
||||
import codechicken.nei.filter.NegatedItemFilter;
|
||||
import codechicken.nei.filter.NothingItemFilter;
|
||||
import codechicken.nei.search.ItemFilterVisitor;
|
||||
import codechicken.nei.search.RecipeFilterVisitor;
|
||||
import codechicken.nei.search.SearchExpressionUtils;
|
||||
|
||||
public class SearchTokenParser {
|
||||
|
||||
@@ -83,7 +90,7 @@ public class SearchTokenParser {
|
||||
}
|
||||
}
|
||||
|
||||
private static class IsRegisteredItemFilter implements ItemFilter {
|
||||
public static class IsRegisteredItemFilter implements ItemFilter {
|
||||
|
||||
public ItemFilter filter;
|
||||
|
||||
@@ -99,27 +106,34 @@ public class SearchTokenParser {
|
||||
}
|
||||
}
|
||||
|
||||
public static final Pattern SPACE_PATTERN = Pattern.compile(" ");
|
||||
|
||||
protected final LRUCache<String, ItemFilter> filtersCache = new LRUCache<>(20);
|
||||
protected final List<ISearchParserProvider> searchProviders;
|
||||
protected final Set<Character> redefinedPrefixes = new HashSet<>();
|
||||
protected final ProvidersCache providersCache = new ProvidersCache();
|
||||
protected final Map<Character, Character> prefixRedefinitions = new HashMap<>();
|
||||
|
||||
public SearchTokenParser(List<ISearchParserProvider> searchProviders) {
|
||||
this.searchProviders = searchProviders;
|
||||
updateRedefinedPrefixes();
|
||||
}
|
||||
|
||||
public SearchTokenParser() {
|
||||
this(new ArrayList<>());
|
||||
updateRedefinedPrefixes();
|
||||
}
|
||||
|
||||
public void addProvider(ISearchParserProvider provider) {
|
||||
this.searchProviders.add(provider);
|
||||
updateRedefinedPrefixes(provider);
|
||||
this.providersCache.clear();
|
||||
this.filtersCache.clear();
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
this.filtersCache.clear();
|
||||
updateRedefinedPrefixes();
|
||||
}
|
||||
|
||||
protected List<ISearchParserProvider> getProviders() {
|
||||
@@ -149,31 +163,86 @@ public class SearchTokenParser {
|
||||
return providersCache.providers;
|
||||
}
|
||||
|
||||
public boolean hasRedefinedPrefix(char ch) {
|
||||
return redefinedPrefixes.contains(ch);
|
||||
}
|
||||
|
||||
public ISearchParserProvider getProvider(char ch) {
|
||||
return getProviders().stream()
|
||||
.filter(
|
||||
provider -> provider.getSearchMode() == SearchMode.PREFIX
|
||||
&& getRedefinedPrefix(provider.getPrefix()) == ch)
|
||||
.findFirst().orElse(null);
|
||||
if (!hasRedefinedPrefix(ch)) {
|
||||
return null;
|
||||
}
|
||||
for (ISearchParserProvider provider : getProviders()) {
|
||||
if (provider.getSearchMode() == SearchMode.PREFIX && getRedefinedPrefix(provider.getPrefix()) == ch) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<ItemFilter> getAlwaysProvidersFilters(String searchText) {
|
||||
final List<ItemFilter> filters = new ArrayList<>();
|
||||
for (ISearchParserProvider provider : getProviders()) {
|
||||
if (provider.getSearchMode() == SearchMode.ALWAYS) {
|
||||
filters.add(provider.getFilter(searchText));
|
||||
}
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
public synchronized ItemFilter getFilter(String filterText) {
|
||||
filterText = EnumChatFormatting.getTextWithoutFormattingCodes(filterText).toLowerCase();
|
||||
if (filterText == null || filterText.isEmpty()) {
|
||||
return new EverythingItemFilter();
|
||||
}
|
||||
final int spaceMode = NEIClientConfig.getIntSetting("inventory.search.spaceMode");
|
||||
final int patternMode = NEIClientConfig.getIntSetting("inventory.search.patternMode");
|
||||
|
||||
return this.filtersCache.computeIfAbsent(filterText, text -> {
|
||||
final String[] parts = text.split("\\|");
|
||||
final List<ItemFilter> searchTokens = Arrays.stream(parts).map(this::parseSearchText).filter(s -> s != null)
|
||||
.collect(Collectors.toList());
|
||||
if (patternMode != 3) {
|
||||
return this.filtersCache.computeIfAbsent(filterText, text -> {
|
||||
final String[] parts = text.split("\\|");
|
||||
final List<ItemFilter> searchTokens = Arrays.stream(parts).map(this::parseSearchText)
|
||||
.filter(s -> s != null).collect(Collectors.toList());
|
||||
|
||||
if (searchTokens.isEmpty()) {
|
||||
return new EverythingItemFilter();
|
||||
} else if (searchTokens.size() == 1) {
|
||||
return new IsRegisteredItemFilter(searchTokens.get(0));
|
||||
} else {
|
||||
return new IsRegisteredItemFilter(new AnyMultiItemFilter(searchTokens));
|
||||
if (searchTokens.isEmpty()) {
|
||||
return new EverythingItemFilter();
|
||||
} else if (searchTokens.size() == 1) {
|
||||
return new IsRegisteredItemFilter(searchTokens.get(0));
|
||||
} else {
|
||||
return new IsRegisteredItemFilter(new AnyMultiItemFilter(searchTokens));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (spaceMode == 1) {
|
||||
filterText = SearchTokenParser.SPACE_PATTERN.matcher(filterText).replaceAll("\\\\ ");
|
||||
}
|
||||
return this.filtersCache.computeIfAbsent(filterText, text -> {
|
||||
final ItemFilterVisitor visitor = new ItemFilterVisitor(this);
|
||||
final ItemFilter searchToken = SearchExpressionUtils.visitSearchExpression(text, visitor);
|
||||
|
||||
});
|
||||
return new IsRegisteredItemFilter(searchToken);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public synchronized IRecipeFilter getRecipeFilter(String filterText) {
|
||||
final int patternMode = NEIClientConfig.getIntSetting("inventory.search.patternMode");
|
||||
if (patternMode != 3) {
|
||||
return new AnyItemRecipeFilter(getFilter(filterText));
|
||||
}
|
||||
filterText = EnumChatFormatting.getTextWithoutFormattingCodes(filterText).toLowerCase();
|
||||
if (filterText == null || filterText.isEmpty()) {
|
||||
return new AnyItemRecipeFilter(new EverythingItemFilter());
|
||||
}
|
||||
final int spaceModeEnabled = NEIClientConfig.getIntSetting("inventory.search.spaceMode");
|
||||
|
||||
if (spaceModeEnabled == 1) {
|
||||
filterText = SearchTokenParser.SPACE_PATTERN.matcher(filterText).replaceAll("\\\\ ");
|
||||
}
|
||||
|
||||
final RecipeFilterVisitor visitor = new RecipeFilterVisitor(this);
|
||||
return SearchExpressionUtils.visitSearchExpression(filterText, visitor);
|
||||
|
||||
}
|
||||
|
||||
@@ -235,21 +304,33 @@ public class SearchTokenParser {
|
||||
}
|
||||
|
||||
private String getPrefixes() {
|
||||
StringBuilder prefixes = new StringBuilder().append('\0');
|
||||
|
||||
for (ISearchParserProvider provider : getProviders()) {
|
||||
if (provider.getSearchMode() == SearchMode.PREFIX) {
|
||||
prefixes.append(getRedefinedPrefix(provider.getPrefix()));
|
||||
}
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (Character redefinedPrefix : redefinedPrefixes) {
|
||||
builder.append(redefinedPrefix);
|
||||
}
|
||||
|
||||
return prefixes.toString();
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public char getRedefinedPrefix(char prefix) {
|
||||
return this.prefixRedefinitions.getOrDefault(prefix, prefix);
|
||||
}
|
||||
|
||||
public void updateRedefinedPrefixes() {
|
||||
this.redefinedPrefixes.clear();
|
||||
for (ISearchParserProvider provider : getProviders()) {
|
||||
if (provider.getSearchMode() == SearchTokenParser.SearchMode.PREFIX) {
|
||||
redefinedPrefixes.add(getRedefinedPrefix(provider.getPrefix()));
|
||||
}
|
||||
}
|
||||
this.redefinedPrefixes.add('\0');
|
||||
}
|
||||
|
||||
public void updateRedefinedPrefixes(ISearchParserProvider provider) {
|
||||
if (provider.getSearchMode() == SearchMode.PREFIX) {
|
||||
this.redefinedPrefixes.add(getRedefinedPrefix(provider.getPrefix()));
|
||||
}
|
||||
}
|
||||
|
||||
private ItemFilter parseSearchText(String filterText) {
|
||||
|
||||
if (filterText.isEmpty()) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -31,13 +32,13 @@ import com.google.gson.JsonParser;
|
||||
import codechicken.core.gui.GuiScrollSlot;
|
||||
import codechicken.lib.gui.GuiDraw;
|
||||
import codechicken.lib.vec.Rectangle4i;
|
||||
import codechicken.nei.ItemList.AnyMultiItemFilter;
|
||||
import codechicken.nei.ItemList.NothingItemFilter;
|
||||
import codechicken.nei.SearchTokenParser.ISearchParserProvider;
|
||||
import codechicken.nei.SearchTokenParser.SearchMode;
|
||||
import codechicken.nei.api.API;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.api.ItemFilter.ItemFilterProvider;
|
||||
import codechicken.nei.filter.AnyMultiItemFilter;
|
||||
import codechicken.nei.filter.NothingItemFilter;
|
||||
import codechicken.nei.guihook.GuiContainerManager;
|
||||
import codechicken.nei.guihook.IContainerTooltipHandler;
|
||||
import codechicken.nei.recipe.GuiCraftingRecipe;
|
||||
@@ -369,12 +370,18 @@ public class SubsetWidget extends Button implements ItemFilterProvider, IContain
|
||||
private static class DefaultParserProvider implements ISearchParserProvider {
|
||||
|
||||
public ItemFilter getFilter(String searchText) {
|
||||
final String pathPart = searchText.replaceAll("\\s+", "").toLowerCase();
|
||||
final int patternMode = NEIClientConfig.getIntSetting("inventory.search.patternMode");
|
||||
Pattern pattern = null;
|
||||
if (patternMode != 3) {
|
||||
searchText = searchText.replaceAll("\\s+", "").toLowerCase();
|
||||
} else {
|
||||
pattern = SearchField.getPattern(searchText, patternMode);
|
||||
}
|
||||
final AnyMultiItemFilter filter = new AnyMultiItemFilter();
|
||||
final Set<ItemStack> filteredItems = new HashSet<>();
|
||||
|
||||
for (SubsetTag tag : tags.values()) {
|
||||
if (tag.filter != null && tag.path.contains(pathPart)) {
|
||||
if (tag.filter != null && matches(tag.path, searchText, pattern)) {
|
||||
filteredItems.addAll(tag.items);
|
||||
filter.filters.add(tag.filter);
|
||||
}
|
||||
@@ -395,6 +402,14 @@ public class SubsetWidget extends Button implements ItemFilterProvider, IContain
|
||||
public SearchMode getSearchMode() {
|
||||
return SearchMode.fromInt(NEIClientConfig.getIntSetting("inventory.search.subsetsSearchMode"));
|
||||
}
|
||||
|
||||
private boolean matches(String name, String searchText, Pattern pattern) {
|
||||
if (pattern != null) {
|
||||
return pattern.matcher(name).find();
|
||||
} else {
|
||||
return name.contains(searchText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ public interface IRecipeFilter {
|
||||
|
||||
public static interface IRecipeFilterProvider {
|
||||
|
||||
public IRecipeFilter getFilter();
|
||||
public IRecipeFilter getRecipeFilter();
|
||||
}
|
||||
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
|
||||
@@ -44,8 +44,6 @@ import com.google.common.collect.ArrayListMultimap;
|
||||
import codechicken.core.featurehack.GameDataManipulator;
|
||||
import codechicken.nei.InfiniteStackSizeHandler;
|
||||
import codechicken.nei.InfiniteToolHandler;
|
||||
import codechicken.nei.ItemList.AnyMultiItemFilter;
|
||||
import codechicken.nei.ItemList.PatternItemFilter;
|
||||
import codechicken.nei.ItemMobSpawner;
|
||||
import codechicken.nei.ItemStackMap;
|
||||
import codechicken.nei.ItemStackSet;
|
||||
@@ -58,6 +56,8 @@ import codechicken.nei.config.ArrayDumper;
|
||||
import codechicken.nei.config.HandlerDumper;
|
||||
import codechicken.nei.config.ItemPanelDumper;
|
||||
import codechicken.nei.config.RegistryDumper;
|
||||
import codechicken.nei.filter.AnyMultiItemFilter;
|
||||
import codechicken.nei.filter.PatternItemFilter;
|
||||
import codechicken.nei.guihook.GuiContainerManager;
|
||||
import codechicken.nei.recipe.BrewingRecipeHandler;
|
||||
import codechicken.nei.recipe.RecipeItemInputHandler;
|
||||
|
||||
@@ -16,9 +16,6 @@ import codechicken.core.gui.GuiWidget;
|
||||
import codechicken.lib.gui.GuiDraw;
|
||||
import codechicken.lib.vec.Rectangle4i;
|
||||
import codechicken.nei.CollapsibleItems;
|
||||
import codechicken.nei.ItemList.AllMultiItemFilter;
|
||||
import codechicken.nei.ItemList.AnyMultiItemFilter;
|
||||
import codechicken.nei.ItemList.NegatedItemFilter;
|
||||
import codechicken.nei.ItemsGrid;
|
||||
import codechicken.nei.ItemsGrid.ItemsGridSlot;
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
@@ -28,6 +25,9 @@ import codechicken.nei.PresetsList.Preset;
|
||||
import codechicken.nei.SearchField.GuiSearchField;
|
||||
import codechicken.nei.TextField;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.filter.AllMultiItemFilter;
|
||||
import codechicken.nei.filter.AnyMultiItemFilter;
|
||||
import codechicken.nei.filter.NegatedItemFilter;
|
||||
import codechicken.nei.guihook.GuiContainerManager;
|
||||
import codechicken.nei.guihook.IContainerTooltipHandler;
|
||||
import codechicken.nei.util.TextHistory;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
|
||||
public class AllIngredientsRecipeFilter extends AllRecipeFilter implements IRecipeFilter {
|
||||
|
||||
public AllIngredientsRecipeFilter(ItemFilter filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
List<PositionedStack> others) {
|
||||
|
||||
return allMatch(ingredients);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
|
||||
public class AllMultiItemFilter implements ItemFilter {
|
||||
|
||||
public final List<ItemFilter> filters;
|
||||
|
||||
public AllMultiItemFilter(List<ItemFilter> filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
public AllMultiItemFilter(ItemFilter... filters) {
|
||||
this(new LinkedList<>(Arrays.asList(filters)));
|
||||
}
|
||||
|
||||
public AllMultiItemFilter() {
|
||||
this(new LinkedList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
for (ItemFilter filter : filters) try {
|
||||
if (filter != null && !filter.matches(item)) return false;
|
||||
} catch (Exception e) {
|
||||
NEIClientConfig.logger
|
||||
.error("Exception filtering " + item + " with " + filter + " (" + e.getMessage() + ")", e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
|
||||
public class AllMultiRecipeFilter implements IRecipeFilter {
|
||||
|
||||
public final List<IRecipeFilter> filters;
|
||||
|
||||
public AllMultiRecipeFilter(List<IRecipeFilter> filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
public AllMultiRecipeFilter(IRecipeFilter filters) {
|
||||
this(Arrays.asList(filters));
|
||||
}
|
||||
|
||||
public AllMultiRecipeFilter() {
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
List<PositionedStack> others) {
|
||||
for (IRecipeFilter filter : filters) {
|
||||
try {
|
||||
if (filter != null && !filter.matches(handler, ingredients, result, others)) return false;
|
||||
} catch (Exception e) {
|
||||
NEIClientConfig.logger.error("Exception filtering " + handler + " with " + filter, e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
|
||||
public class AllOthersRecipeFilter extends AllRecipeFilter implements IRecipeFilter {
|
||||
|
||||
public AllOthersRecipeFilter(ItemFilter filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
List<PositionedStack> others) {
|
||||
|
||||
return allMatch(others);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
|
||||
public abstract class AllRecipeFilter {
|
||||
|
||||
private final ItemFilter filter;
|
||||
|
||||
protected AllRecipeFilter(ItemFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
protected boolean allMatch(List<PositionedStack> items) {
|
||||
for (PositionedStack pStack : items) {
|
||||
if (!allMatch(pStack)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean allMatch(PositionedStack pStack) {
|
||||
if (pStack == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (ItemStack stack : pStack.items) {
|
||||
if (!filter.matches(stack)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
|
||||
public class AllSmartResultRecipeFilter extends AllRecipeFilter implements IRecipeFilter {
|
||||
|
||||
public AllSmartResultRecipeFilter(ItemFilter filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
List<PositionedStack> others) {
|
||||
if (result != null) {
|
||||
return allMatch(result);
|
||||
} else {
|
||||
// GT recipes use others as a result
|
||||
return allMatch(others);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
|
||||
public class AnyIngredientsRecipeFilter extends AnyRecipeFilter implements IRecipeFilter {
|
||||
|
||||
public AnyIngredientsRecipeFilter(ItemFilter filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
List<PositionedStack> others) {
|
||||
|
||||
return anyMatch(ingredients);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
|
||||
public class AnyItemRecipeFilter extends AnyRecipeFilter implements IRecipeFilter {
|
||||
|
||||
public AnyItemRecipeFilter(ItemFilter filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
List<PositionedStack> others) {
|
||||
return anyMatch(ingredients) || anyMatch(result) || anyMatch(others);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
|
||||
public class AnyMultiItemFilter implements ItemFilter {
|
||||
|
||||
public final List<ItemFilter> filters;
|
||||
|
||||
public AnyMultiItemFilter(List<ItemFilter> filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
public AnyMultiItemFilter() {
|
||||
this(new LinkedList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
for (ItemFilter filter : filters) try {
|
||||
if (filter != null && filter.matches(item)) return true;
|
||||
} catch (Exception e) {
|
||||
NEIClientConfig.logger
|
||||
.error("Exception filtering " + item + " with " + filter + " (" + e.getMessage() + ")", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
|
||||
public class AnyOthersRecipeFilter extends AnyRecipeFilter implements IRecipeFilter {
|
||||
|
||||
public AnyOthersRecipeFilter(ItemFilter filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
List<PositionedStack> others) {
|
||||
|
||||
return anyMatch(others);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
|
||||
public abstract class AnyRecipeFilter {
|
||||
|
||||
private final ItemFilter filter;
|
||||
|
||||
protected AnyRecipeFilter(ItemFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
protected boolean anyMatch(List<PositionedStack> items) {
|
||||
for (PositionedStack pStack : items) {
|
||||
if (anyMatch(pStack)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean anyMatch(PositionedStack pStack) {
|
||||
if (pStack == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (ItemStack stack : pStack.items) {
|
||||
if (filter.matches(stack)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
|
||||
public class AnySmartResultRecipeFilter extends AnyRecipeFilter implements IRecipeFilter {
|
||||
|
||||
public AnySmartResultRecipeFilter(ItemFilter filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
List<PositionedStack> others) {
|
||||
if (result != null) {
|
||||
return anyMatch(result);
|
||||
} else {
|
||||
// GT recipes use others as a result
|
||||
return anyMatch(others);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.SearchField;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
|
||||
public class DoubleItemFilter implements ItemFilter {
|
||||
|
||||
private final ItemFilter filter;
|
||||
private final PatternItemFilter patternFilter;
|
||||
|
||||
public DoubleItemFilter(ItemFilter filter, String itemFilter) {
|
||||
this.filter = filter;
|
||||
this.patternFilter = new PatternItemFilter(SearchField.getPattern(itemFilter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
if (filter.matches(item)) {
|
||||
return patternFilter.matches(item);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
|
||||
public class EverythingItemFilter implements ItemFilter {
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
|
||||
public class NegatedItemFilter implements ItemFilter {
|
||||
|
||||
private final ItemFilter filter;
|
||||
|
||||
public NegatedItemFilter(ItemFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
return this.filter == null || !this.filter.matches(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
|
||||
public class NothingItemFilter implements ItemFilter {
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package codechicken.nei.filter;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
|
||||
public class PatternItemFilter implements ItemFilter {
|
||||
|
||||
public final Pattern pattern;
|
||||
|
||||
public PatternItemFilter(Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack item) {
|
||||
String displayName = EnumChatFormatting.getTextWithoutFormattingCodes(item.getDisplayName());
|
||||
|
||||
if (displayName != null && !displayName.isEmpty() && pattern.matcher(displayName).find()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.hasDisplayName()) {
|
||||
displayName = EnumChatFormatting
|
||||
.getTextWithoutFormattingCodes(item.getItem().getItemStackDisplayName(item));
|
||||
|
||||
return displayName != null && !displayName.isEmpty() && pattern.matcher(displayName).find();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.api.TaggedInventoryArea;
|
||||
import codechicken.nei.drawable.DrawableBuilder;
|
||||
import codechicken.nei.drawable.DrawableResource;
|
||||
import codechicken.nei.filter.AllMultiRecipeFilter;
|
||||
import codechicken.nei.guihook.GuiContainerManager;
|
||||
import codechicken.nei.guihook.IContainerTooltipHandler;
|
||||
import codechicken.nei.guihook.IGuiClientSide;
|
||||
@@ -118,86 +119,6 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
|
||||
|
||||
private int yShift = 0;
|
||||
|
||||
public static class ItemRecipeFilter implements IRecipeFilter {
|
||||
|
||||
public ItemFilter filter;
|
||||
|
||||
public ItemRecipeFilter(ItemFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
List<PositionedStack> others) {
|
||||
|
||||
if (matchPositionedStack(ingredients)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matchPositionedStack(result)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matchPositionedStack(others)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean matchPositionedStack(List<PositionedStack> items) {
|
||||
for (PositionedStack pStack : items) {
|
||||
if (matchPositionedStack(pStack)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean matchPositionedStack(PositionedStack pStack) {
|
||||
if (pStack == null) return false;
|
||||
|
||||
for (ItemStack stack : pStack.items) {
|
||||
if (filter.matches(stack)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class AllMultiRecipeFilter implements IRecipeFilter {
|
||||
|
||||
public List<IRecipeFilter> filters;
|
||||
|
||||
public AllMultiRecipeFilter(List<IRecipeFilter> filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
public AllMultiRecipeFilter(IRecipeFilter filters) {
|
||||
this(Arrays.asList(filters));
|
||||
}
|
||||
|
||||
public AllMultiRecipeFilter() {
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IRecipeHandler handler, List<PositionedStack> ingredients, PositionedStack result,
|
||||
List<PositionedStack> others) {
|
||||
for (IRecipeFilter filter : filters) {
|
||||
try {
|
||||
if (filter != null && !filter.matches(handler, ingredients, result, others)) return false;
|
||||
} catch (Exception e) {
|
||||
NEIClientConfig.logger.error("Exception filtering " + handler + " with " + filter, e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected static final RestartableTask updateFilter = new RestartableTask("NEI Recipe Filtering") {
|
||||
|
||||
@Override
|
||||
@@ -214,7 +135,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
|
||||
searchHandler.setSearchIndices(null);
|
||||
guiRecipe.changePage(0);
|
||||
} else {
|
||||
final IRecipeFilter filter = new ItemRecipeFilter(GuiRecipe.searchField.getFilter());
|
||||
final IRecipeFilter filter = GuiRecipe.searchField.getRecipeFilter();
|
||||
final List<Integer> filtered = searchHandler.getSearchResult(filter);
|
||||
|
||||
if (filtered == null) {
|
||||
@@ -546,7 +467,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
|
||||
|
||||
synchronized (recipeFilterers) {
|
||||
for (IRecipeFilterProvider p : recipeFilterers) {
|
||||
IRecipeFilter filter = p.getFilter();
|
||||
IRecipeFilter filter = p.getRecipeFilter();
|
||||
if (filter != null) {
|
||||
recipeFilter.filters.add(filter);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package codechicken.nei.search;
|
||||
|
||||
import codechicken.nei.SearchTokenParser;
|
||||
|
||||
public abstract class AbstractSearchExpressionVisitor<T> extends SearchExpressionParserBaseVisitor<T> {
|
||||
|
||||
protected final SearchTokenParser searchParser;
|
||||
|
||||
protected AbstractSearchExpressionVisitor(SearchTokenParser searchParser) {
|
||||
this.searchParser = searchParser;
|
||||
}
|
||||
|
||||
public SearchTokenParser getSearchParser() {
|
||||
return searchParser;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package codechicken.nei.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.RuleNode;
|
||||
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.SearchTokenParser;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.filter.AllMultiItemFilter;
|
||||
import codechicken.nei.filter.AnyMultiItemFilter;
|
||||
import codechicken.nei.filter.NegatedItemFilter;
|
||||
import codechicken.nei.filter.NothingItemFilter;
|
||||
|
||||
public class ItemFilterVisitor extends AbstractSearchExpressionVisitor<ItemFilter> {
|
||||
|
||||
private static final Pattern REGEX_ESCAPED_SPACE_PATTERN = Pattern.compile("([^\\\\](?:\\\\\\\\)+)?\\\\ ");
|
||||
private static final Pattern PLAIN_TEXT_ESCAPED_PATTERN = Pattern.compile("\\\\(.)");
|
||||
private static final Pattern ESCAPED_QUOTE_PATTERN = Pattern.compile("\\\\\"");
|
||||
private static final Pattern ESCAPED_SPACE_PATTERN = Pattern.compile("\\\\ ");
|
||||
|
||||
public ItemFilterVisitor(SearchTokenParser searchParser) {
|
||||
super(searchParser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter visitChildren(RuleNode node) {
|
||||
if (node instanceof ParserRuleContext) {
|
||||
return visitChildren((ParserRuleContext) node, null);
|
||||
} else {
|
||||
return defaultResult();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter visitOrExpression(SearchExpressionParser.OrExpressionContext ctx) {
|
||||
return visitChildren(ctx, AnyMultiItemFilter::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter visitSequenceExpression(SearchExpressionParser.SequenceExpressionContext ctx) {
|
||||
return visitChildren(ctx, AllMultiItemFilter::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter visitNegateExpression(SearchExpressionParser.NegateExpressionContext ctx) {
|
||||
return new NegatedItemFilter(visitChildren(ctx));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter visitPrefixedExpression(SearchExpressionParser.PrefixedExpressionContext ctx) {
|
||||
if (ctx.token() != null) {
|
||||
final String cleanText = getTokenCleanText(ctx.token());
|
||||
final SearchTokenParser.ISearchParserProvider provider = searchParser.getProvider(ctx.token().prefix);
|
||||
if (cleanText == null || provider == null) {
|
||||
return defaultResult();
|
||||
}
|
||||
return provider.getFilter(cleanText);
|
||||
}
|
||||
return defaultResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter visitToken(SearchExpressionParser.TokenContext ctx) {
|
||||
final String cleanText = getTokenCleanText(ctx);
|
||||
if (cleanText != null) {
|
||||
return getAlwaysProvidersFilter(cleanText);
|
||||
}
|
||||
return defaultResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemFilter defaultResult() {
|
||||
return new NothingItemFilter();
|
||||
}
|
||||
|
||||
private String getTokenCleanText(SearchExpressionParser.TokenContext ctx) {
|
||||
String cleanText = null;
|
||||
if (ctx.PLAIN_TEXT() != null) {
|
||||
cleanText = Pattern.quote(
|
||||
PLAIN_TEXT_ESCAPED_PATTERN.matcher(ctx.PLAIN_TEXT().getSymbol().getText())
|
||||
// Unescape everything in search expression
|
||||
.replaceAll("$1"));
|
||||
} else if (ctx.regex() != null) {
|
||||
if (ctx.regex().REGEX_CONTENT() != null) {
|
||||
cleanText = ctx.regex().REGEX_CONTENT().getSymbol().getText();
|
||||
final int spaceModeEnabled = NEIClientConfig.getIntSetting("inventory.search.spaceMode");
|
||||
// Unescape spaces
|
||||
if (spaceModeEnabled == 1) {
|
||||
cleanText = REGEX_ESCAPED_SPACE_PATTERN.matcher(cleanText).replaceAll("$1 ");
|
||||
}
|
||||
}
|
||||
} else if (ctx.quoted() != null) {
|
||||
if (ctx.quoted().QUOTED_CONTENT() != null) {
|
||||
cleanText = ctx.quoted().QUOTED_CONTENT().getSymbol().getText();
|
||||
// Unescape quotes
|
||||
cleanText = ESCAPED_QUOTE_PATTERN.matcher(cleanText).replaceAll("\"");
|
||||
cleanText = Pattern.quote(cleanText);
|
||||
final int spaceModeEnabled = NEIClientConfig.getIntSetting("inventory.search.spaceMode");
|
||||
// Unescape spaces
|
||||
if (spaceModeEnabled == 1) {
|
||||
cleanText = ESCAPED_SPACE_PATTERN.matcher(cleanText).replaceAll(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
return cleanText;
|
||||
}
|
||||
|
||||
private ItemFilter visitChildren(ParserRuleContext node, Function<List<ItemFilter>, ItemFilter> filterConstructor) {
|
||||
if (node.children != null && !node.children.isEmpty()) {
|
||||
// By default return any found rule child filter (there should be only one)
|
||||
if (filterConstructor == null) {
|
||||
for (final ParseTree child : node.children) {
|
||||
if (child instanceof RuleNode) {
|
||||
return visit(child);
|
||||
}
|
||||
}
|
||||
return defaultResult();
|
||||
// Otherwise create a filter out of rule childrens' filters
|
||||
} else {
|
||||
final List<ItemFilter> filters = new ArrayList<>();
|
||||
for (final ParseTree child : node.children) {
|
||||
if (child instanceof RuleNode) {
|
||||
filters.add(visit(child));
|
||||
}
|
||||
}
|
||||
return constructFilter(filters, filterConstructor);
|
||||
}
|
||||
}
|
||||
return defaultResult();
|
||||
}
|
||||
|
||||
private ItemFilter getAlwaysProvidersFilter(String searchText) {
|
||||
final List<ItemFilter> filters = searchParser.getAlwaysProvidersFilters(searchText);
|
||||
return constructFilter(filters, AnyMultiItemFilter::new);
|
||||
}
|
||||
|
||||
private ItemFilter constructFilter(List<ItemFilter> filters, Function<List<ItemFilter>, ItemFilter> constructor) {
|
||||
if (!filters.isEmpty()) {
|
||||
// Propagate the result up
|
||||
if (filters.size() == 1) {
|
||||
return filters.get(0);
|
||||
}
|
||||
return constructor.apply(filters);
|
||||
} else {
|
||||
return defaultResult();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package codechicken.nei.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import codechicken.nei.SearchTokenParser;
|
||||
import codechicken.nei.api.IRecipeFilter;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.filter.AllIngredientsRecipeFilter;
|
||||
import codechicken.nei.filter.AllMultiRecipeFilter;
|
||||
import codechicken.nei.filter.AllOthersRecipeFilter;
|
||||
import codechicken.nei.filter.AllSmartResultRecipeFilter;
|
||||
import codechicken.nei.filter.AnyIngredientsRecipeFilter;
|
||||
import codechicken.nei.filter.AnyItemRecipeFilter;
|
||||
import codechicken.nei.filter.AnyOthersRecipeFilter;
|
||||
import codechicken.nei.filter.AnySmartResultRecipeFilter;
|
||||
import codechicken.nei.filter.EverythingItemFilter;
|
||||
|
||||
public class RecipeFilterVisitor extends AbstractSearchExpressionVisitor<IRecipeFilter> {
|
||||
|
||||
private final ItemFilterVisitor itemFilterVisitor;
|
||||
|
||||
public RecipeFilterVisitor(SearchTokenParser searchParser) {
|
||||
super(searchParser);
|
||||
itemFilterVisitor = new ItemFilterVisitor(searchParser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRecipeFilter visitRecipeSearchExpression(SearchExpressionParser.RecipeSearchExpressionContext ctx) {
|
||||
if (ctx.recipeClauseExpression() != null) {
|
||||
final List<IRecipeFilter> filters = new ArrayList<>();
|
||||
for (SearchExpressionParser.RecipeClauseExpressionContext clauseCtx : ctx.recipeClauseExpression()) {
|
||||
filters.add(createRecipeFilter(clauseCtx.searchExpression()));
|
||||
}
|
||||
return constructFilter(filters);
|
||||
}
|
||||
return defaultResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IRecipeFilter defaultResult() {
|
||||
return new AnyItemRecipeFilter(new EverythingItemFilter());
|
||||
}
|
||||
|
||||
private IRecipeFilter createRecipeFilter(SearchExpressionParser.SearchExpressionContext ctx) {
|
||||
if (ctx == null) {
|
||||
return defaultResult();
|
||||
}
|
||||
final ItemFilter itemFilter = itemFilterVisitor.visitSearchExpression(ctx);
|
||||
switch (ctx.type) {
|
||||
case 0:
|
||||
return getAllOrAnyFilter(
|
||||
ctx.allRecipe,
|
||||
itemFilter,
|
||||
AnyIngredientsRecipeFilter::new,
|
||||
AllIngredientsRecipeFilter::new);
|
||||
case 1:
|
||||
return getAllOrAnyFilter(
|
||||
ctx.allRecipe,
|
||||
itemFilter,
|
||||
AnySmartResultRecipeFilter::new,
|
||||
AllSmartResultRecipeFilter::new);
|
||||
case 2:
|
||||
return getAllOrAnyFilter(
|
||||
ctx.allRecipe,
|
||||
itemFilter,
|
||||
AnyOthersRecipeFilter::new,
|
||||
AllOthersRecipeFilter::new);
|
||||
// Doesn't support all by default
|
||||
case 3:
|
||||
return getAllOrAnyFilter(ctx.allRecipe, itemFilter, AnyItemRecipeFilter::new, AnyItemRecipeFilter::new);
|
||||
default:
|
||||
return defaultResult();
|
||||
}
|
||||
}
|
||||
|
||||
private IRecipeFilter getAllOrAnyFilter(boolean allRecipe, ItemFilter itemFilter,
|
||||
Function<ItemFilter, IRecipeFilter> createAnyFilter, Function<ItemFilter, IRecipeFilter> createAllFilter) {
|
||||
if (allRecipe) {
|
||||
return createAllFilter.apply(itemFilter);
|
||||
} else {
|
||||
return createAnyFilter.apply(itemFilter);
|
||||
}
|
||||
}
|
||||
|
||||
private IRecipeFilter constructFilter(List<IRecipeFilter> filters) {
|
||||
if (!filters.isEmpty()) {
|
||||
// Propagate the result up
|
||||
if (filters.size() == 1) {
|
||||
return filters.get(0);
|
||||
}
|
||||
return new AllMultiRecipeFilter(filters);
|
||||
} else {
|
||||
return defaultResult();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package codechicken.nei.search;
|
||||
|
||||
import org.antlr.v4.runtime.BaseErrorListener;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
|
||||
public class SearchExpressionErrorListener extends BaseErrorListener {
|
||||
|
||||
@Override
|
||||
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine,
|
||||
String msg, RecognitionException e) {
|
||||
NEIClientConfig.logger
|
||||
.error("NEI Search grammar exception on line " + line + " char " + charPositionInLine + ": " + msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package codechicken.nei.search;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.RuleNode;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.SearchTokenParser;
|
||||
import codechicken.nei.SearchTokenParser.ISearchParserProvider;
|
||||
|
||||
public class SearchExpressionFormatVisitor extends AbstractSearchExpressionVisitor<String> {
|
||||
|
||||
private static final Pattern REGEX_ESCAPED_SPACE_PATTERN = Pattern.compile("([^\\\\](?:\\\\\\\\)+)?\\\\ ");
|
||||
private static final Pattern ESCAPED_SPACE_PATTERN = Pattern.compile("\\\\ ");
|
||||
|
||||
public SearchExpressionFormatVisitor(SearchTokenParser searchParser) {
|
||||
super(searchParser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitChildren(RuleNode node) {
|
||||
if (node instanceof ParserRuleContext) {
|
||||
return visitChildren((ParserRuleContext) node, null);
|
||||
} else {
|
||||
return defaultResult();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitPrefixedExpression(SearchExpressionParser.PrefixedExpressionContext ctx) {
|
||||
final EnumChatFormatting format = getFormatting(ctx.prefix, null);
|
||||
return format + String.valueOf(ctx.prefix) + visitToken(ctx.token());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitToken(SearchExpressionParser.TokenContext ctx) {
|
||||
return getTokenCleanText(ctx, ctx.prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitRegex(SearchExpressionParser.RegexContext ctx) {
|
||||
return visitChildren(ctx, ctx.prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitQuoted(SearchExpressionParser.QuotedContext ctx) {
|
||||
return visitChildren(ctx, ctx.prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String defaultResult() {
|
||||
return "";
|
||||
}
|
||||
|
||||
private String getTokenCleanText(SearchExpressionParser.TokenContext ctx, Character prefix) {
|
||||
String cleanText = null;
|
||||
final int spaceModeEnabled = NEIClientConfig.getIntSetting("inventory.search.spaceMode");
|
||||
if (ctx.PLAIN_TEXT() != null) {
|
||||
cleanText = ctx.PLAIN_TEXT().getSymbol().getText();
|
||||
// Unescape spaces
|
||||
if (spaceModeEnabled == 1) {
|
||||
cleanText = ESCAPED_SPACE_PATTERN.matcher(cleanText).replaceAll(" ");
|
||||
}
|
||||
final EnumChatFormatting format = getFormatting(prefix, EnumChatFormatting.RESET);
|
||||
return format + cleanText;
|
||||
} else if (ctx.regex() != null) {
|
||||
cleanText = visitRegex(ctx.regex());
|
||||
if (spaceModeEnabled == 1) {
|
||||
cleanText = REGEX_ESCAPED_SPACE_PATTERN.matcher(cleanText).replaceAll("$1 ");
|
||||
}
|
||||
} else if (ctx.quoted() != null) {
|
||||
cleanText = visitQuoted(ctx.quoted());
|
||||
if (spaceModeEnabled == 1) {
|
||||
cleanText = ESCAPED_SPACE_PATTERN.matcher(cleanText).replaceAll(" ");
|
||||
}
|
||||
}
|
||||
return cleanText;
|
||||
}
|
||||
|
||||
private String formatChild(ParseTree child, Character prefix) {
|
||||
if (child instanceof TerminalNode) {
|
||||
final TerminalNode node = (TerminalNode) child;
|
||||
final int type = node.getSymbol().getType();
|
||||
final String format = Optional.ofNullable(
|
||||
// check if highlight is defined for the token
|
||||
Optional.ofNullable(SearchExpressionUtils.getHighlight(type))
|
||||
// check if highlight is defined for the prefix
|
||||
.orElse(getFormatting(prefix, null))
|
||||
// use default highlight otherwise
|
||||
).map(EnumChatFormatting::toString).orElse("");
|
||||
|
||||
return format + node.getSymbol().getText();
|
||||
} else {
|
||||
return visit(child);
|
||||
}
|
||||
}
|
||||
|
||||
private String visitChildren(ParserRuleContext node, Character prefix) {
|
||||
if (node.children != null && !node.children.isEmpty()) {
|
||||
return node.children.stream().map(child -> formatChild(child, prefix)).collect(Collectors.joining());
|
||||
}
|
||||
return defaultResult();
|
||||
}
|
||||
|
||||
private EnumChatFormatting getFormatting(Character prefix, EnumChatFormatting defaultFormatting) {
|
||||
if (prefix != null) {
|
||||
if (prefix == '\0') {
|
||||
return EnumChatFormatting.RESET;
|
||||
}
|
||||
final ISearchParserProvider provider = searchParser.getProvider(prefix);
|
||||
if (provider != null) {
|
||||
return provider.getHighlightedColor();
|
||||
}
|
||||
}
|
||||
return defaultFormatting;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package codechicken.nei.search;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.SearchTokenParser;
|
||||
|
||||
public class SearchExpressionUtils {
|
||||
|
||||
private static final Map<Integer, EnumChatFormatting> HIGHLIGHT_MAP = new HashMap<>();
|
||||
static {
|
||||
for (HIGHLIGHTS elem : HIGHLIGHTS.values()) {
|
||||
for (int parserType : elem.parserTypes) {
|
||||
HIGHLIGHT_MAP.put(parserType, elem.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static enum HIGHLIGHTS {
|
||||
|
||||
RECIPE(EnumChatFormatting.GREEN, SearchExpressionParser.RECIPE_INGREDIENTS,
|
||||
SearchExpressionParser.RECIPE_RESULT, SearchExpressionParser.RECIPE_OTHERS),
|
||||
OR(EnumChatFormatting.GRAY, SearchExpressionParser.OR),
|
||||
BRACKETS(EnumChatFormatting.GRAY, SearchExpressionParser.LEFT_BRACKET, SearchExpressionParser.RIGHT_BRACKET),
|
||||
NEGATE(EnumChatFormatting.BLUE, SearchExpressionParser.DASH),
|
||||
REGEX(EnumChatFormatting.AQUA, SearchExpressionParser.REGEX_LEFT, SearchExpressionParser.REGEX_RIGHT),
|
||||
QUOTED(EnumChatFormatting.GOLD, SearchExpressionParser.QUOTE_LEFT, SearchExpressionParser.QUOTE_RIGHT);
|
||||
|
||||
public final EnumChatFormatting f;
|
||||
public final int[] parserTypes;
|
||||
|
||||
private HIGHLIGHTS(EnumChatFormatting format, int... parserTypes) {
|
||||
this.f = format;
|
||||
this.parserTypes = parserTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.f.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static EnumChatFormatting getHighlight(Integer parserType) {
|
||||
return HIGHLIGHT_MAP.get(parserType);
|
||||
}
|
||||
|
||||
public static <T> T visitSearchExpression(String text, AbstractSearchExpressionVisitor<T> visitor) {
|
||||
SearchExpressionParser parser = createSearchExpressionParser(text, visitor.getSearchParser(), visitor);
|
||||
return visitor.visitRecipeSearchExpression(parser.recipeSearchExpression());
|
||||
}
|
||||
|
||||
private static <T> SearchExpressionParser createSearchExpressionParser(String text, SearchTokenParser searchParser,
|
||||
SearchExpressionParserBaseVisitor<T> visitor) {
|
||||
final boolean doLogExceptions = NEIClientConfig.getBooleanSetting("inventory.search.logSearchExceptions");
|
||||
final CharStream inputStream = CharStreams.fromString(text);
|
||||
final SearchExpressionErrorListener errorListener = new SearchExpressionErrorListener();
|
||||
final SearchExpressionLexer lexer = new SearchExpressionLexer(inputStream, searchParser);
|
||||
lexer.removeErrorListeners();
|
||||
if (doLogExceptions) {
|
||||
lexer.addErrorListener(errorListener);
|
||||
}
|
||||
final CommonTokenStream tokenStream = new CommonTokenStream(lexer);
|
||||
final SearchExpressionParser parser = new SearchExpressionParser(tokenStream);
|
||||
parser.removeErrorListeners();
|
||||
if (doLogExceptions) {
|
||||
parser.addErrorListener(errorListener);
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,11 +14,11 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraftforge.oredict.OreDictionary;
|
||||
|
||||
import codechicken.nei.ItemList.AllMultiItemFilter;
|
||||
import codechicken.nei.ItemList.AnyMultiItemFilter;
|
||||
import codechicken.nei.ItemList.NegatedItemFilter;
|
||||
import codechicken.nei.NEIServerUtils;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.filter.AllMultiItemFilter;
|
||||
import codechicken.nei.filter.AnyMultiItemFilter;
|
||||
import codechicken.nei.filter.NegatedItemFilter;
|
||||
import cpw.mods.fml.common.registry.FMLControlledNamespacedRegistry;
|
||||
import cpw.mods.fml.common.registry.GameData;
|
||||
|
||||
|
||||
@@ -287,6 +287,7 @@ nei.options.inventory.search.patternMode=Search Mode
|
||||
nei.options.inventory.search.patternMode.0=Plain
|
||||
nei.options.inventory.search.patternMode.1=Extended
|
||||
nei.options.inventory.search.patternMode.2=Regex
|
||||
nei.options.inventory.search.patternMode.3=Extended+
|
||||
nei.options.inventory.search.spaceMode=Space Works like
|
||||
nei.options.inventory.search.spaceMode.0=AND
|
||||
nei.options.inventory.search.spaceMode.0.tip=Space between non-prefixed tokens works like "AND"
|
||||
|
||||
@@ -0,0 +1,410 @@
|
||||
package codechicken.nei;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Named.named;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.Language;
|
||||
import net.minecraft.client.resources.LanguageManager;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.FieldSource;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import codechicken.nei.SearchField.SearchParserProvider;
|
||||
import codechicken.nei.api.ItemFilter;
|
||||
import codechicken.nei.filter.AllMultiItemFilter;
|
||||
import codechicken.nei.filter.AnyMultiItemFilter;
|
||||
import codechicken.nei.filter.PatternItemFilter;
|
||||
import codechicken.nei.search.IdentifierFilter;
|
||||
|
||||
@DisplayName("Search expression")
|
||||
public class SearchTokenParserTest {
|
||||
|
||||
private static class ModeValue {
|
||||
|
||||
private final String name;
|
||||
private final int value;
|
||||
|
||||
public ModeValue(int value, String name) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static final ModeValue[] spaceModes = { new ModeValue(0, "space works like AND"),
|
||||
new ModeValue(1, "space works like SPACE"), new ModeValue(2, "space works like AND-X") };
|
||||
private static final ModeValue[] spaceIsAndMode = { spaceModes[0] };
|
||||
private static final ModeValue[] spaceIsSpaceMode = { spaceModes[1] };
|
||||
private static final ModeValue[] patternModes = { new ModeValue(0, "Plain Text mode"),
|
||||
new ModeValue(1, "Extended mode"), new ModeValue(2, "Regex mode"), new ModeValue(3, "Extended+ mode") };
|
||||
private static final ModeValue[] extendedPlusPatternMode = { patternModes[3] };
|
||||
private static final ModeValue[] extendedPatternModes = { patternModes[1], patternModes[3] };
|
||||
|
||||
private static final SearchParserProvider defaultParserProvider = parserProvider(
|
||||
'\0',
|
||||
"default",
|
||||
SearchTokenParser.SearchMode.ALWAYS,
|
||||
PatternItemFilter::new);
|
||||
|
||||
private static final Function<Pattern, ItemFilter> customCreateFilter = (pattern) -> new IdentifierFilter(pattern) {
|
||||
|
||||
@Override
|
||||
public boolean matches(ItemStack stack) {
|
||||
return pattern.matcher(getStringIdentifier(stack)).find();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStringIdentifier(ItemStack stack) {
|
||||
if (stack == itemA) {
|
||||
return "A";
|
||||
}
|
||||
return "F";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getIdentifier(ItemStack stack) {
|
||||
return getStringIdentifier(stack);
|
||||
}
|
||||
};
|
||||
|
||||
private static MockedStatic<NEIClientConfig> config;
|
||||
private static Language language;
|
||||
private static ItemStack itemA;
|
||||
private static ItemStack itemB;
|
||||
private static ItemStack itemC;
|
||||
private static ItemStack itemAB;
|
||||
private static ItemStack itemABC;
|
||||
private static ItemStack itemQuestionMark;
|
||||
|
||||
private static List<Arguments> combinationsProvider(ModeValue[] spaceModes, ModeValue[] patternModes) {
|
||||
List<Arguments> combinations = new ArrayList<>();
|
||||
for (ModeValue s : spaceModes) {
|
||||
for (ModeValue p : patternModes) {
|
||||
combinations
|
||||
.add(Arguments.arguments(named(s.getName(), s.getValue()), named(p.getName(), p.getValue())));
|
||||
}
|
||||
}
|
||||
return combinations;
|
||||
}
|
||||
|
||||
private static final List<Arguments> allModesTestProvider = combinationsProvider(spaceModes, patternModes);
|
||||
|
||||
private static final List<Arguments> extendedPlusAllSpaceModesTestProvider = combinationsProvider(
|
||||
spaceModes,
|
||||
extendedPlusPatternMode);
|
||||
|
||||
private static final List<Arguments> spaceIsSpaceAllModesTestProvider = combinationsProvider(
|
||||
spaceIsSpaceMode,
|
||||
patternModes);
|
||||
|
||||
private static final List<Arguments> spaceIsAndAllModesTestProvider = combinationsProvider(
|
||||
spaceIsAndMode,
|
||||
patternModes);
|
||||
|
||||
private static final List<Arguments> extendedAllSpaceModesTestProvider = combinationsProvider(
|
||||
spaceModes,
|
||||
extendedPatternModes);
|
||||
|
||||
private static final List<Arguments> spaceIsAndExtendedPlusModeTestProvider = combinationsProvider(
|
||||
spaceIsAndMode,
|
||||
extendedPlusPatternMode);
|
||||
|
||||
@BeforeAll
|
||||
public static void initItems() {
|
||||
itemA = mock(ItemStack.class);
|
||||
when(itemA.getDisplayName()).thenReturn("a");
|
||||
itemB = mock(ItemStack.class);
|
||||
when(itemB.getDisplayName()).thenReturn("b");
|
||||
itemC = mock(ItemStack.class);
|
||||
when(itemC.getDisplayName()).thenReturn("c");
|
||||
itemAB = mock(ItemStack.class);
|
||||
when(itemAB.getDisplayName()).thenReturn("a b");
|
||||
itemABC = mock(ItemStack.class);
|
||||
when(itemABC.getDisplayName()).thenReturn("a b c");
|
||||
itemQuestionMark = mock(ItemStack.class);
|
||||
when(itemQuestionMark.getDisplayName()).thenReturn("?");
|
||||
|
||||
MockedStatic<Minecraft> staticMinecraft = mockStatic(Minecraft.class);
|
||||
Minecraft minecraft = mock(Minecraft.class);
|
||||
staticMinecraft.when(() -> Minecraft.getMinecraft()).thenReturn(minecraft);
|
||||
LanguageManager languageManager = mock(LanguageManager.class);
|
||||
when(minecraft.getLanguageManager()).thenReturn(languageManager);
|
||||
language = mock(Language.class);
|
||||
when(languageManager.getCurrentLanguage()).thenReturn(language);
|
||||
when(language.getLanguageCode()).thenReturn("en");
|
||||
config = mockStatic(NEIClientConfig.class);
|
||||
config.when(() -> NEIClientConfig.getBooleanSetting(eq("inventory.search.logSearchExceptions")))
|
||||
.thenReturn(false);
|
||||
|
||||
}
|
||||
|
||||
// All modes tests
|
||||
|
||||
@DisplayName("No providers")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("allModesTestProvider")
|
||||
public void testWithoutProviders(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(spaceMode, patternMode, searchParser, "test");
|
||||
|
||||
assertEquals(0, matchedItems.size());
|
||||
}
|
||||
|
||||
@DisplayName("Or")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("allModesTestProvider")
|
||||
public void testOr(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
searchParser.addProvider(defaultParserProvider);
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
|
||||
spaceMode,
|
||||
patternMode,
|
||||
searchParser,
|
||||
itemA.getDisplayName() + "|" + itemC.getDisplayName());
|
||||
assertEquals(4, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemA));
|
||||
assertTrue(matchedItems.contains(itemC));
|
||||
assertTrue(matchedItems.contains(itemAB));
|
||||
assertTrue(matchedItems.contains(itemABC));
|
||||
}
|
||||
|
||||
@DisplayName("Negate")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("allModesTestProvider")
|
||||
public void testNegate(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
searchParser.addProvider(defaultParserProvider);
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
|
||||
spaceMode,
|
||||
patternMode,
|
||||
searchParser,
|
||||
"-" + itemA.getDisplayName());
|
||||
|
||||
assertEquals(3, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemB));
|
||||
assertTrue(matchedItems.contains(itemC));
|
||||
assertTrue(matchedItems.contains(itemQuestionMark));
|
||||
}
|
||||
|
||||
@DisplayName("Custom prefix provider")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("allModesTestProvider")
|
||||
public void testCustomPrefixProvider(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
searchParser.addProvider(defaultParserProvider);
|
||||
searchParser
|
||||
.addProvider(parserProvider('?', "custom", SearchTokenParser.SearchMode.PREFIX, customCreateFilter));
|
||||
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(spaceMode, patternMode, searchParser, "?A");
|
||||
|
||||
assertEquals(1, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemA));
|
||||
}
|
||||
|
||||
@DisplayName("Space mode")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("spaceIsSpaceAllModesTestProvider")
|
||||
public void testSpaceMode(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
searchParser.addProvider(defaultParserProvider);
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
|
||||
spaceMode,
|
||||
patternMode,
|
||||
searchParser,
|
||||
itemAB.getDisplayName());
|
||||
assertEquals(2, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemAB));
|
||||
assertTrue(matchedItems.contains(itemABC));
|
||||
}
|
||||
|
||||
@DisplayName("Spaced expression")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("spaceIsAndAllModesTestProvider")
|
||||
public void testSpacedExpression(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
searchParser.addProvider(defaultParserProvider);
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
|
||||
spaceMode,
|
||||
patternMode,
|
||||
searchParser,
|
||||
itemA.getDisplayName() + " " + itemC.getDisplayName());
|
||||
assertEquals(1, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemABC));
|
||||
}
|
||||
|
||||
// Extended modes tests
|
||||
|
||||
@DisplayName("Regex")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("extendedAllSpaceModesTestProvider")
|
||||
public void testRegex(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
searchParser.addProvider(defaultParserProvider);
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
|
||||
spaceMode,
|
||||
patternMode,
|
||||
searchParser,
|
||||
"r/z?\\s" + itemB.getDisplayName() + "/");
|
||||
|
||||
assertEquals(2, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemAB));
|
||||
assertTrue(matchedItems.contains(itemABC));
|
||||
}
|
||||
|
||||
// Extended+ specific tests
|
||||
|
||||
@DisplayName("Sequence")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("extendedPlusAllSpaceModesTestProvider")
|
||||
public void testSequence(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
searchParser.addProvider(defaultParserProvider);
|
||||
searchParser
|
||||
.addProvider(parserProvider('?', "custom", SearchTokenParser.SearchMode.PREFIX, customCreateFilter));
|
||||
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
|
||||
spaceMode,
|
||||
patternMode,
|
||||
searchParser,
|
||||
"\"" + itemA.getDisplayName() + "\"?A");
|
||||
|
||||
assertEquals(1, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemA));
|
||||
}
|
||||
|
||||
@DisplayName("Brackets")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("extendedPlusAllSpaceModesTestProvider")
|
||||
public void testBrackets(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
searchParser.addProvider(defaultParserProvider);
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
|
||||
spaceMode,
|
||||
patternMode,
|
||||
searchParser,
|
||||
"{" + itemA.getDisplayName() + "|" + itemC.getDisplayName() + "}" + itemB.getDisplayName());
|
||||
|
||||
assertEquals(2, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemAB));
|
||||
assertTrue(matchedItems.contains(itemABC));
|
||||
}
|
||||
|
||||
@DisplayName("Quoted")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("extendedPlusAllSpaceModesTestProvider")
|
||||
public void testQuoted(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
searchParser.addProvider(defaultParserProvider);
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
|
||||
spaceMode,
|
||||
patternMode,
|
||||
searchParser,
|
||||
"\"" + itemB.getDisplayName() + " " + itemC.getDisplayName());
|
||||
|
||||
assertEquals(1, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemABC));
|
||||
}
|
||||
|
||||
@DisplayName("Escaped symbols")
|
||||
@ParameterizedTest(name = "{argumentsWithNames}")
|
||||
@FieldSource("spaceIsAndExtendedPlusModeTestProvider")
|
||||
public void testEscapedSymbols(int spaceMode, int patternMode) {
|
||||
SearchTokenParser searchParser = new SearchTokenParser();
|
||||
searchParser.addProvider(defaultParserProvider);
|
||||
searchParser
|
||||
.addProvider(parserProvider('?', "custom", SearchTokenParser.SearchMode.PREFIX, customCreateFilter));
|
||||
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
|
||||
spaceMode,
|
||||
patternMode,
|
||||
searchParser,
|
||||
'\\' + itemQuestionMark.getDisplayName());
|
||||
|
||||
assertEquals(1, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemQuestionMark));
|
||||
|
||||
matchedItems = testFilterAgainstAllItems(spaceMode, patternMode, searchParser, '\\' + " ");
|
||||
|
||||
assertEquals(2, matchedItems.size());
|
||||
assertTrue(matchedItems.contains(itemAB));
|
||||
assertTrue(matchedItems.contains(itemABC));
|
||||
}
|
||||
|
||||
private static Set<ItemStack> testFilterAgainstAllItems(int spaceMode, int patternMode,
|
||||
SearchTokenParser searchParser, String search) {
|
||||
config.when(() -> NEIClientConfig.getIntSetting(eq("inventory.search.patternMode"))).thenReturn(patternMode);
|
||||
config.when(() -> NEIClientConfig.getIntSetting(eq("inventory.search.spaceMode"))).thenReturn(spaceMode);
|
||||
|
||||
ItemFilter filter = searchParser.getFilter(search);
|
||||
if (filter instanceof SearchTokenParser.IsRegisteredItemFilter) {
|
||||
filter = ((SearchTokenParser.IsRegisteredItemFilter) filter).filter;
|
||||
}
|
||||
Set<ItemStack> matchedItems = Stream.of(itemA, itemB, itemC, itemAB, itemABC, itemQuestionMark)
|
||||
.filter(filter::matches).collect(Collectors.toSet());
|
||||
return matchedItems;
|
||||
}
|
||||
|
||||
private static SearchParserProvider parserProvider(char prefix, String name,
|
||||
SearchTokenParser.SearchMode searchMode, Function<Pattern, ItemFilter> createFilter) {
|
||||
return new SearchParserProvider(prefix, name, EnumChatFormatting.RESET, createFilter) {
|
||||
|
||||
@Override
|
||||
public SearchTokenParser.SearchMode getSearchMode() {
|
||||
return searchMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Language> getMatchingLanguages() {
|
||||
List<Language> languages = new ArrayList<>();
|
||||
languages.add(language);
|
||||
return languages;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private static String printResult(Set<ItemStack> items) {
|
||||
return items.stream().map(ItemStack::getDisplayName).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
private static String printFilterContents(ItemFilter filter) {
|
||||
if (filter instanceof AnyMultiItemFilter) {
|
||||
return "ANY: ("
|
||||
+ ((AnyMultiItemFilter) filter).filters.stream().map(SearchTokenParserTest::printFilterContents)
|
||||
.collect(Collectors.joining(","))
|
||||
+ ")";
|
||||
}
|
||||
if (filter instanceof AllMultiItemFilter) {
|
||||
return "ALL: ("
|
||||
+ ((AllMultiItemFilter) filter).filters.stream().map(SearchTokenParserTest::printFilterContents)
|
||||
.collect(Collectors.joining(","))
|
||||
+ ")";
|
||||
}
|
||||
if (filter instanceof PatternItemFilter) {
|
||||
return "pattern(" + ((PatternItemFilter) filter).pattern + ")";
|
||||
}
|
||||
return filter.toString();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user