From ca7030df72af6813a2ec9a96c89032b68b8e9b6e Mon Sep 17 00:00:00 2001 From: slprime <31038811+slprime@users.noreply.github.com> Date: Mon, 23 Dec 2024 12:21:28 +0200 Subject: [PATCH] Add Space Mode Settings to Search (#571) Co-authored-by: slprime Co-authored-by: boubou19 --- .../java/codechicken/nei/NEIClientConfig.java | 18 +++ .../codechicken/nei/SearchTextFormatter.java | 49 +++---- .../codechicken/nei/SearchTokenParser.java | 130 ++++++++++++++---- .../codechicken/nei/config/OptionButton.java | 9 +- .../codechicken/nei/config/OptionCycled.java | 12 ++ src/main/resources/assets/nei/lang/en_US.lang | 7 + 6 files changed, 160 insertions(+), 65 deletions(-) diff --git a/src/main/java/codechicken/nei/NEIClientConfig.java b/src/main/java/codechicken/nei/NEIClientConfig.java index df7808d9f..00126d4ef 100644 --- a/src/main/java/codechicken/nei/NEIClientConfig.java +++ b/src/main/java/codechicken/nei/NEIClientConfig.java @@ -457,6 +457,7 @@ public boolean onClick(int button) { } if (state()) { + NEIClientConfig.setIntSetting("inventory.search.spaceMode", 1); NEIClientConfig.setIntSetting("inventory.search.modNameSearchMode", 0); NEIClientConfig.setIntSetting("inventory.search.tooltipSearchMode", 0); NEIClientConfig.setIntSetting("inventory.search.identifierSearchMode", 0); @@ -468,6 +469,7 @@ public boolean onClick(int button) { SearchField.searchParser.prefixRedefinitions.put('@', '%'); SearchField.searchParser.clearCache(); } else { + NEIClientConfig.setIntSetting("inventory.search.spaceMode", 0); NEIClientConfig.setIntSetting("inventory.search.modNameSearchMode", 1); NEIClientConfig.setIntSetting("inventory.search.tooltipSearchMode", 0); NEIClientConfig.setIntSetting("inventory.search.identifierSearchMode", 0); @@ -483,6 +485,22 @@ public boolean onClick(int button) { }); + tag.getTag("inventory.search.spaceMode").setComment("Search Space Rules").getIntValue(0); + API.addOption(new OptionCycled("inventory.search.spaceMode", 3, true) { + + @Override + public boolean onClick(int button) { + // SearchField.searchParser.clearCache(); + return super.onClick(button); + } + + @Override + public boolean isEnabled() { + return !tag.getTag("inventory.search.format").getBooleanValue(); + } + + }); + tag.getTag("inventory.search.modNameSearchMode").setComment("Search mode for Mod Names (prefix: @)") .getIntValue(1); API.addOption(new OptionCycled("inventory.search.modNameSearchMode", 3, true) { diff --git a/src/main/java/codechicken/nei/SearchTextFormatter.java b/src/main/java/codechicken/nei/SearchTextFormatter.java index 3866fca21..74705a20a 100644 --- a/src/main/java/codechicken/nei/SearchTextFormatter.java +++ b/src/main/java/codechicken/nei/SearchTextFormatter.java @@ -1,12 +1,12 @@ package codechicken.nei; +import java.util.List; import java.util.StringJoiner; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import net.minecraft.util.EnumChatFormatting; import codechicken.nei.FormattedTextField.TextFormatter; +import codechicken.nei.SearchTokenParser.SearchToken; public class SearchTextFormatter implements TextFormatter { @@ -17,63 +17,50 @@ public SearchTextFormatter(SearchTokenParser searchParser) { } public String format(String text) { - final String[] parts = text.split("\\|"); - final Pattern splitPattern = searchParser.getSplitPattern(); + final String[] parts = (text + "| ").split("\\|"); StringJoiner formattedText = new StringJoiner(EnumChatFormatting.GRAY + "|"); - for (String filterText : parts) { - Matcher filterMatcher = splitPattern.matcher(filterText); + for (int i = 0; i < parts.length - 1; i++) { + final String filterText = parts[i]; + final List tokens = searchParser.splitSearchText(filterText); StringBuilder formattedPart = new StringBuilder(); int startIndex = 0; - while (filterMatcher.find()) { - boolean ignore = "-".equals(filterMatcher.group(2)); - String firstChar = filterMatcher.group(3); - String token = filterMatcher.group(4); - boolean quotes = token.length() > 1 && token.startsWith("\"") && token.endsWith("\""); - - if (quotes) { - token = token.substring(1, token.length() - 1); - } - - formattedPart.append(filterText.substring(startIndex, filterMatcher.start())); + for (SearchToken token : tokens) { + formattedPart.append(filterText.substring(startIndex, token.start)); EnumChatFormatting tokenColor = EnumChatFormatting.RESET; - if (!firstChar.isEmpty()) { - tokenColor = searchParser.getProvider(firstChar.charAt(0)).getHighlightedColor(); + if (token.firstChar != null) { + tokenColor = searchParser.getProvider(token.firstChar).getHighlightedColor(); } - if (ignore) { + if (token.ignore) { formattedPart.append(EnumChatFormatting.BLUE + "-"); } - if (!firstChar.isEmpty()) { - formattedPart.append(tokenColor + firstChar); + if (token.firstChar != null) { + formattedPart.append(tokenColor + String.valueOf(token.firstChar)); } - if (quotes) { + if (token.quotes) { formattedPart.append(EnumChatFormatting.GOLD + "\""); } - if (!token.isEmpty()) { - formattedPart.append(tokenColor + token); + if (!token.rawText.isEmpty()) { + formattedPart.append(tokenColor + token.rawText); } - if (quotes) { + if (token.quotes) { formattedPart.append(EnumChatFormatting.GOLD + "\""); } - startIndex = filterMatcher.end(); + startIndex = token.end; } formattedPart.append(filterText.substring(startIndex, filterText.length())); formattedText.add(formattedPart); } - if (text.endsWith("|")) { - formattedText.add(""); - } - return formattedText.toString(); } } diff --git a/src/main/java/codechicken/nei/SearchTokenParser.java b/src/main/java/codechicken/nei/SearchTokenParser.java index 7da0c5c5b..d22c1f6d3 100644 --- a/src/main/java/codechicken/nei/SearchTokenParser.java +++ b/src/main/java/codechicken/nei/SearchTokenParser.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -21,8 +22,6 @@ import codechicken.nei.ItemList.NegatedItemFilter; import codechicken.nei.ItemList.NothingItemFilter; import codechicken.nei.api.ItemFilter; -import gnu.trove.map.TCharCharMap; -import gnu.trove.map.hash.TCharCharHashMap; public class SearchTokenParser { @@ -41,6 +40,20 @@ public static SearchMode fromInt(int value) { } } + public static class SearchToken { + + public boolean ignore = false; + public boolean quotes = false; + public Character firstChar = null; + + public String[] words; + + public String rawText = ""; + public int start = 0; + public int end = 0; + + } + public static interface ISearchParserProvider { public ItemFilter getFilter(String searchText); @@ -99,7 +112,7 @@ protected boolean removeEldestEntry(Map.Entry eldest) { protected final List searchProviders; protected final ProvidersCache providersCache = new ProvidersCache(); - protected final TCharCharMap prefixRedefinitions = new TCharCharHashMap(); + protected final Map prefixRedefinitions = new HashMap<>(); public SearchTokenParser(List searchProviders) { this.searchProviders = searchProviders; @@ -169,7 +182,64 @@ public synchronized ItemFilter getFilter(String filterText) { } - public Pattern getSplitPattern() { + public List splitSearchText(String filterText) { + + if (filterText.isEmpty()) { + return Collections.emptyList(); + } + + final List tokens = new ArrayList<>(); + final String prefixes = getPrefixes(); + final int spaceMode = NEIClientConfig.getIntSetting("inventory.search.spaceMode"); + // The regular expression first tries to match a string that starts with a space or the beginning of the string, + // followed by a sequence of characters that do not contain special characters -"@#$%&, and ends with a space + // and the characters -"@#$%& or the end of the string. + final String patternPart1 = "(?(^|\\s+)(?:[^" + Pattern.quote(" -\"" + prefixes) + + "]).+?(?=\\s+[" + + Pattern.quote("-\"" + prefixes) + + "]|$))"; + // If the first condition is not met, it tries to match a sequence of - characters, followed by @#$%& + // characters, then either a quoted string or non-space characters. + final String patternPart2 = "((?-*)(?[" + Pattern.quote(prefixes) + + "]*)(?\\\".*?(?:\\\"|$)|\\S+\\s*))"; + final Pattern pattern = Pattern.compile(spaceMode == 0 ? patternPart2 : (patternPart1 + "|" + patternPart2)); + final Matcher filterMatcher = pattern.matcher(filterText); + + while (filterMatcher.find()) { + String firstChar = filterMatcher.group("firstChar"); + SearchToken token = new SearchToken(); + token.start = filterMatcher.start(); + token.end = filterMatcher.end(); + token.ignore = "-".equals(filterMatcher.group("ignore")); + token.rawText = spaceMode == 0 ? null : filterMatcher.group("tokenA"); + + if (firstChar != null && !firstChar.isEmpty()) { + token.firstChar = firstChar.charAt(0); + } + + if (token.rawText == null) { // spaceMode == 0 + token.rawText = filterMatcher.group("tokenB"); + token.quotes = token.rawText.length() > 1 && token.rawText.startsWith("\"") + && token.rawText.endsWith("\""); + + if (token.quotes) { + token.rawText = token.rawText.substring(1, token.rawText.length() - 1); + } + + token.words = new String[] { token.rawText.trim() }; + } else if (spaceMode == 2) { + token.words = token.rawText.trim().split("\\s+"); + } else { + token.words = new String[] { token.rawText.trim() }; + } + + tokens.add(token); + } + + return tokens; + } + + private String getPrefixes() { StringBuilder prefixes = new StringBuilder().append('\0'); for (ISearchParserProvider provider : getProviders()) { @@ -178,14 +248,11 @@ public Pattern getSplitPattern() { } } - return Pattern.compile("((-*)([" + Pattern.quote(prefixes.toString()) + "]*)(\\\".*?(?:\\\"|$)|\\S+))"); + return prefixes.toString(); } public char getRedefinedPrefix(char prefix) { - if (this.prefixRedefinitions.containsKey(prefix)) { - return this.prefixRedefinitions.get(prefix); - } - return prefix; + return this.prefixRedefinitions.getOrDefault(prefix, prefix); } private ItemFilter parseSearchText(String filterText) { @@ -194,28 +261,19 @@ private ItemFilter parseSearchText(String filterText) { return null; } - final Matcher filterMatcher = getSplitPattern().matcher(filterText); final AllMultiItemFilter searchTokens = new AllMultiItemFilter(); + final List tokens = splitSearchText(filterText); - while (filterMatcher.find()) { - boolean ignore = "-".equals(filterMatcher.group(2)); - String firstChar = filterMatcher.group(3); - String token = filterMatcher.group(4); - boolean quotes = token.length() > 1 && token.startsWith("\"") && token.endsWith("\""); - - if (quotes) { - token = token.substring(1, token.length() - 1); - } - - if (!token.isEmpty()) { - ItemFilter result = parseToken(firstChar, token); + for (SearchToken token : tokens) { + if (!token.rawText.isEmpty()) { + ItemFilter result = parseToken(token); - if (ignore) { + if (token.ignore) { searchTokens.filters.add(new NegatedItemFilter(result)); } else { searchTokens.filters.add(result); } - } else if (!ignore) { + } else if (!token.ignore) { searchTokens.filters.add(new NothingItemFilter()); } } @@ -223,17 +281,17 @@ private ItemFilter parseSearchText(String filterText) { return searchTokens; } - private ItemFilter parseToken(String firstChar, String token) { - final ISearchParserProvider provider = firstChar.isEmpty() ? null : this.getProvider(firstChar.charAt(0)); + private ItemFilter parseToken(SearchToken token) { + final ISearchParserProvider provider = token.firstChar == null ? null : this.getProvider(token.firstChar); if (provider == null || provider.getSearchMode() == SearchMode.NEVER) { final List filters = new ArrayList<>(); for (ISearchParserProvider _provider : getProviders()) { if (_provider.getSearchMode() == SearchMode.ALWAYS) { - ItemFilter filter = _provider.getFilter(token); + AllMultiItemFilter filter = generateFilters(_provider, token.words); - if (filter != null) { + if (!filter.filters.isEmpty()) { filters.add(filter); } } @@ -241,7 +299,21 @@ private ItemFilter parseToken(String firstChar, String token) { return filters.isEmpty() ? new NothingItemFilter() : new AnyMultiItemFilter(filters); } else { - return provider.getFilter(token); + return generateFilters(provider, token.words); } } + + private AllMultiItemFilter generateFilters(ISearchParserProvider provider, String[] words) { + final AllMultiItemFilter filters = new AllMultiItemFilter(); + + for (String work : words) { + final ItemFilter filter = provider.getFilter(work); + + if (filter != null) { + filters.filters.add(filter); + } + } + + return filters; + } } diff --git a/src/main/java/codechicken/nei/config/OptionButton.java b/src/main/java/codechicken/nei/config/OptionButton.java index 46b4bc8bf..40c60ca67 100644 --- a/src/main/java/codechicken/nei/config/OptionButton.java +++ b/src/main/java/codechicken/nei/config/OptionButton.java @@ -75,26 +75,25 @@ public String getButtonText() { } public String getTooltip() { - String tip = null; if (tooltip != null) { String s = translateN(tooltip); if (!s.equals(namespaced(tooltip))) { - tip = s; + return s; } } - if (tip == null && getPrefix() != null) { + if (getPrefix() != null) { final int width = getStringWidth(getPrefix()); final Rectangle b = buttonSize(); if (width >= b.x) { - tip = translateN(name); + return translateN(name); } } - return tip; + return null; } public void drawPrefix() { diff --git a/src/main/java/codechicken/nei/config/OptionCycled.java b/src/main/java/codechicken/nei/config/OptionCycled.java index 9574d4641..3aa1a68a3 100644 --- a/src/main/java/codechicken/nei/config/OptionCycled.java +++ b/src/main/java/codechicken/nei/config/OptionCycled.java @@ -29,6 +29,18 @@ public String getPrefix() { return prefixed ? translateN(name) : null; } + @Override + public String getTooltip() { + String tooltip = name + "." + value() + ".tip"; + String s = translateN(tooltip); + + if (!s.equals(namespaced(tooltip))) { + return s; + } + + return super.getTooltip(); + } + @Override public boolean onClick(int button) { return cycle(); diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index ab968623f..d2b9c2977 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -224,6 +224,13 @@ 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.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" +nei.options.inventory.search.spaceMode.1=Space +nei.options.inventory.search.spaceMode.1.tip=Space between non-prefixed tokens works like "space" +nei.options.inventory.search.spaceMode.2=AND-X +nei.options.inventory.search.spaceMode.2.tip=Space between non-prefixed tokens works like "AND" but all tokens must be within the same category nei.options.inventory.search.quoteDropItemName=Quote Drop Item Name nei.options.inventory.search.quoteDropItemName.true=Yes nei.options.inventory.search.quoteDropItemName.false=No