Files
NotEnoughItems/src/main/java/codechicken/nei/SearchField.java
T
2024-05-30 19:24:20 +03:00

525 lines
18 KiB
Java

package codechicken.nei;
import static codechicken.nei.NEIClientConfig.world;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import net.minecraft.client.Minecraft;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
import net.minecraftforge.fluids.FluidStack;
import codechicken.lib.gui.GuiDraw;
import codechicken.nei.FormattedTextField.TextFormatter;
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.ItemList.PatternItemFilter;
import codechicken.nei.api.API;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.api.ItemFilter.ItemFilterProvider;
import codechicken.nei.recipe.StackInfo;
import codechicken.nei.search.IdentifierFilter;
import codechicken.nei.search.ModNameFilter;
import codechicken.nei.search.OreDictionaryFilter;
import codechicken.nei.search.TooltipFilter;
import codechicken.nei.util.TextHistory;
import scala.collection.mutable.StringBuilder;
public class SearchField extends TextField implements ItemFilterProvider {
public enum SearchMode {
ALWAYS,
PREFIX,
NEVER;
public static SearchMode fromInt(int value) {
switch (value) {
case 0:
return ALWAYS;
case 1:
return PREFIX;
case 2:
return NEVER;
default:
return ALWAYS;
}
}
}
/**
* Interface for returning a custom filter based on search field text
*/
@Deprecated
public static interface ISearchProvider {
/**
* @return false if this filter should only be used if no other non-default filters match the search string
*/
public boolean isPrimary();
/**
* @return An item filter for items matching SearchTex null to ignore this provider
*/
public ItemFilter getFilter(String searchText);
}
public static interface ISearchParserProvider {
public ItemFilter getFilter(String searchText);
public char getPrefix();
public EnumChatFormatting getHighlightedColor();
public SearchMode getSearchMode();
}
public static class SearchParserProvider implements ISearchParserProvider {
protected final List<Function<Pattern, ItemFilter>> subFilters = new ArrayList<>();
protected final String name;
protected final char prefix;
protected final EnumChatFormatting highlightedColor;
public SearchParserProvider(char prefix, String name, EnumChatFormatting highlightedColor,
Function<Pattern, ItemFilter> createFilter) {
this.prefix = prefix;
this.name = name;
this.highlightedColor = highlightedColor;
this.addSubFilter(createFilter);
}
public void addSubFilter(Function<Pattern, ItemFilter> subFilter) {
this.subFilters.add(subFilter);
}
@Override
public ItemFilter getFilter(String searchText) {
Pattern pattern = SearchField.getPattern(searchText);
if (pattern != null) {
return this.createFilter(pattern);
}
return null;
}
protected ItemFilter createFilter(Pattern pattern) {
final List<ItemFilter> filters = new ArrayList<>();
for (Function<Pattern, ItemFilter> createFilter : this.subFilters) {
ItemFilter filter = createFilter.apply(pattern);
if (filter != null) {
filters.add(filter);
}
}
return filters.isEmpty() ? null : new AnyMultiItemFilter(filters);
}
@Override
public char getPrefix() {
return this.prefix;
}
@Override
public EnumChatFormatting getHighlightedColor() {
return this.highlightedColor;
}
@Override
public SearchMode getSearchMode() {
return SearchMode.fromInt(NEIClientConfig.getIntSetting("inventory.search." + this.name + "SearchMode"));
}
}
public static class SearchTextFormatter implements TextFormatter {
public String format(String text) {
final String[] parts = text.split("\\|");
StringJoiner formattedText = new StringJoiner(EnumChatFormatting.GRAY + "|");
for (String filterText : parts) {
Matcher filterMatcher = SearchField.getFilterSplitPattern().matcher(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()));
EnumChatFormatting tokenColor = EnumChatFormatting.RESET;
if (!firstChar.isEmpty()) {
tokenColor = SearchField.searchParserProviders.get(firstChar.charAt(0)).getHighlightedColor();
}
if (ignore) {
formattedPart.append(EnumChatFormatting.BLUE + "-");
}
if (!firstChar.isEmpty()) {
formattedPart.append(tokenColor + firstChar);
}
if (quotes) {
formattedPart.append(EnumChatFormatting.GOLD + "\"");
}
if (!token.isEmpty()) {
formattedPart.append(tokenColor + token);
}
if (quotes) {
formattedPart.append(EnumChatFormatting.GOLD + "\"");
}
startIndex = filterMatcher.end();
}
formattedPart.append(filterText.substring(startIndex, filterText.length()));
formattedText.add(formattedPart);
}
if (text.endsWith("|")) {
formattedText.add("");
}
return formattedText.toString();
}
}
@Deprecated
public static List<ISearchProvider> searchProviders = new LinkedList<>();
public static final Map<Character, ISearchParserProvider> searchParserProviders = new HashMap<>();
private static final TextHistory history = new TextHistory();
private boolean isVisible = true;
private long lastclicktime;
public SearchField(String ident) {
super(ident);
API.addItemFilter(this);
API.addSearchProvider(
new SearchParserProvider(
'\0',
"default",
EnumChatFormatting.RESET,
(pattern) -> new PatternItemFilter(pattern)) {
@Override
public SearchMode getSearchMode() {
return SearchMode.ALWAYS;
}
});
API.addSearchProvider(
new SearchParserProvider(
'@',
"modName",
EnumChatFormatting.LIGHT_PURPLE,
(pattern) -> new ModNameFilter(pattern)));
API.addSearchProvider(
new SearchParserProvider(
'$',
"oreDict",
EnumChatFormatting.AQUA,
(pattern) -> new OreDictionaryFilter(pattern)));
API.addSearchProvider(
new SearchParserProvider(
'#',
"tooltip",
EnumChatFormatting.YELLOW,
(pattern) -> new TooltipFilter(pattern)));
API.addSearchProvider(
new SearchParserProvider(
'&',
"identifier",
EnumChatFormatting.GOLD,
(pattern) -> new IdentifierFilter(pattern)));
}
@Override
protected void initInternalTextField() {
field = new FormattedTextField(Minecraft.getMinecraft().fontRenderer, 0, 0, 0, 0);
((FormattedTextField) field).setFormatter(new SearchTextFormatter());
field.setMaxStringLength(maxSearchLength);
field.setCursorPositionZero();
}
public static boolean searchInventories() {
return world.nbt.getBoolean("searchinventories");
}
public boolean isVisible() {
return isVisible;
}
public void setVisible(boolean visible) {
isVisible = visible;
}
@Override
public int getTextColour() {
if (ItemPanels.itemPanel.getItems().isEmpty()) {
return focused() ? 0xFFcc3300 : 0xFF993300;
} else {
return focused() ? 0xFFE0E0E0 : 0xFF909090;
}
}
@Override
public void draw(int mousex, int mousey) {
super.draw(mousex, mousey);
if (searchInventories()) {
GuiDraw.drawGradientRect(x - 1, y - 1, 1, h + 2, 0xFFFFFF00, 0xFFC0B000); // Left
GuiDraw.drawGradientRect(x - 1, y - 1, w + 2, 1, 0xFFFFFF00, 0xFFC0B000); // Top
GuiDraw.drawGradientRect(x + w, y - 1, 1, h + 2, 0xFFFFFF00, 0xFFC0B000); // Left
GuiDraw.drawGradientRect(x - 1, y + h, w + 2, 1, 0xFFFFFF00, 0xFFC0B000); // Bottom
}
}
@Override
public boolean handleClick(int mousex, int mousey, int button) {
if (button == 0) {
if (focused() && (System.currentTimeMillis() - lastclicktime < 400)) { // double click
NEIClientConfig.world.nbt.setBoolean("searchinventories", !searchInventories());
}
lastclicktime = System.currentTimeMillis();
}
return super.handleClick(mousex, mousey, button);
}
@Override
public void onTextChange(String oldText) {
final String newText = text();
if (newText.length() > 0) NEIClientConfig.logger.debug("Searching for " + newText);
NEIClientConfig.setSearchExpression(newText);
ItemList.updateFilter.restart();
}
@Override
public void lastKeyTyped(int keyID, char keyChar) {
if (isVisible() && NEIClientConfig.isKeyHashDown("gui.search")) {
setFocus(true);
}
if (focused() && NEIClientConfig.isKeyHashDown("gui.getprevioussearch")) {
handleNavigateHistory(TextHistory.Direction.PREVIOUS);
}
if (focused() && NEIClientConfig.isKeyHashDown("gui.getnextsearch")) {
handleNavigateHistory(TextHistory.Direction.NEXT);
}
}
@Override
public String filterText(String s) {
return EnumChatFormatting.getTextWithoutFormattingCodes(s);
}
public static Pattern getPattern(String search) {
switch (NEIClientConfig.getIntSetting("inventory.search.patternMode")) {
case 0: // plain
search = Pattern.quote(search);
break;
case 1: // extended
search = search.replace(".", "");
final Matcher matcher = Pattern.compile("(\\?|\\*)").matcher(search);
String cleanedString = "";
int lastEndIndex = 0;
while (matcher.find()) {
cleanedString += Pattern.quote(search.substring(lastEndIndex, matcher.start()));
switch (matcher.group(0).charAt(0)) {
case '?':
cleanedString += ".";
break;
case '*':
cleanedString += ".+?";
break;
default:
break;
}
lastEndIndex = matcher.end();
}
search = cleanedString + Pattern.quote(search.substring(lastEndIndex, search.length()));
break;
}
if (!search.isEmpty()) {
try {
return Pattern.compile(search, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
} catch (PatternSyntaxException ignored) {}
}
return null;
}
public static String getEscapedSearchText(ItemStack stack) {
final FluidStack fluidStack = StackInfo.getFluid(stack);
String displayName;
if (fluidStack != null) {
displayName = fluidStack.getLocalizedName();
} else {
displayName = stack.getDisplayName();
}
return getEscapedSearchText(displayName);
}
public static String getEscapedSearchText(String text) {
text = EnumChatFormatting.getTextWithoutFormattingCodes(text);
switch (NEIClientConfig.getIntSetting("inventory.search.patternMode")) {
case 1:
text = text.replaceAll("[\\?|\\*]", "\\\\$0");
break;
case 2:
text = text.replaceAll("[{}()\\[\\].+*?^$\\\\|]", "\\\\$0");
break;
}
if (text.contains(" ") && NEIClientConfig.getBooleanSetting("inventory.search.quoteDropItemName")) {
text = "\"" + text + "\"";
}
return text;
}
@Override
public ItemFilter getFilter() {
return getFilter(text());
}
protected static Pattern getFilterSplitPattern() {
StringJoiner prefixes = new StringJoiner("");
for (ISearchParserProvider provider : SearchField.searchParserProviders.values()) {
if (provider.getSearchMode() == SearchMode.PREFIX) {
prefixes.add(String.valueOf(provider.getPrefix()));
}
}
return Pattern.compile("((-*)([" + Pattern.quote(prefixes.toString()) + "]*)(\\\".*?(?:\\\"|$)|\\S+))");
}
public static ItemFilter getFilter(String filterText) {
final String[] parts = EnumChatFormatting.getTextWithoutFormattingCodes(filterText).toLowerCase().split("\\|");
final List<ItemFilter> searchTokens = Arrays.stream(parts).map(SearchField::parseSearchTokens)
.filter(s -> s != null).collect(Collectors.toCollection(ArrayList::new));
if (searchTokens.isEmpty()) {
return new EverythingItemFilter();
} else {
return new AnyMultiItemFilter(searchTokens);
}
}
private static ItemFilter parseSearchTokens(String filterText) {
if (filterText.isEmpty()) {
return null;
}
final Matcher filterMatcher = getFilterSplitPattern().matcher(filterText);
final AllMultiItemFilter searchTokens = new AllMultiItemFilter();
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);
if (ignore) {
searchTokens.filters.add(new NegatedItemFilter(result));
} else {
searchTokens.filters.add(result);
}
} else if (!ignore) {
searchTokens.filters.add(new NothingItemFilter());
}
}
return searchTokens;
}
private static ItemFilter parseToken(String firstChar, String token) {
final ISearchParserProvider provider = firstChar.isEmpty() ? null
: SearchField.searchParserProviders.get(firstChar.charAt(0));
if (provider == null || provider.getSearchMode() == SearchMode.NEVER) {
final List<ItemFilter> filters = new ArrayList<>();
for (ISearchParserProvider _provider : SearchField.searchParserProviders.values()) {
if (_provider.getSearchMode() == SearchMode.ALWAYS) {
ItemFilter filter = _provider.getFilter(token);
if (filter != null) {
filters.add(filter);
}
}
}
return filters.isEmpty() ? new NothingItemFilter() : new AnyMultiItemFilter(filters);
} else {
ItemFilter filter = provider.getFilter(token);
return filter != null ? filter : new NothingItemFilter();
}
}
@Override
public void setFocus(boolean focus) {
final boolean previousFocus = field.isFocused();
if (previousFocus != focus) {
history.add(text());
}
super.setFocus(focus);
}
private boolean handleNavigateHistory(TextHistory.Direction direction) {
if (focused()) {
return history.get(direction, text()).map(newText -> {
setText(newText);
return true;
}).orElse(false);
}
return false;
}
}