Skip to content

Commit

Permalink
Improved ME Terminal Search to be more similar to REI/EMI (#7883)
Browse files Browse the repository at this point in the history
This was created based on #7311, instead of relying on the functionality
of JEI, REI, etc. I modified the search functionality to search similar
to RS, JEI, etc.

The reason behind this PR is the same reason as the previous PR (#7311):
>This can be useful because it is annoying when you try to search
something with the modid and the name like it is possible in JEI. For
example "@ae2 crystal". Without it, it tries to search a mod with the
name "ae2 crystal".

---------

Co-authored-by: Sebastian Hartte <[email protected]>
  • Loading branch information
JoelMorrisey and shartte authored Jun 16, 2024
1 parent 952a66c commit 43a1e29
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 166 deletions.
5 changes: 2 additions & 3 deletions src/generated/resources/assets/ae2/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -459,16 +459,15 @@
"gui.ae2.SearchSettingsClearExternal": "Clear %s search on open",
"gui.ae2.SearchSettingsRememberSearch": "Remember last search",
"gui.ae2.SearchSettingsReplaceWithExternal": "Replace with %s search",
"gui.ae2.SearchSettingsSearchTooltips": "Search in tooltips",
"gui.ae2.SearchSettingsSyncWithExternal": "Sync with %s search",
"gui.ae2.SearchSettingsTitle": "Search Settings",
"gui.ae2.SearchSettingsUseExternalSearch": "Use %s",
"gui.ae2.SearchSettingsUseInternalSearch": "Use AE",
"gui.ae2.SearchTooltip": "Search in Name",
"gui.ae2.SearchTooltipIncludingTooltips": "Search in Name and Tooltip",
"gui.ae2.SearchTooltipItemId": "Use * to search by id (*cell)",
"gui.ae2.SearchTooltipModId": "Use @ to search by mod (@ae2)",
"gui.ae2.SearchTooltipTag": "Use # to search by tag (#ores)",
"gui.ae2.SearchTooltipTag": "Use $ to search by tags ($ingot)",
"gui.ae2.SearchTooltipToolTips": "Use # to search in tooltips (#looting)",
"gui.ae2.SelectAmount": "Select Amount",
"gui.ae2.SelectedCraftingCPU": "Crafting CPU: %s",
"gui.ae2.SerialNumber": "Serial Number: %s",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,11 +405,11 @@ private void updateSearch() {

// This can change due to changes in the search settings sub-screen
this.searchField.setTooltipMessage(List.of(
config.isSearchTooltips() ? GuiText.SearchTooltipIncludingTooltips.text()
: GuiText.SearchTooltip.text(),
GuiText.SearchTooltip.text(),
GuiText.SearchTooltipModId.text(),
GuiText.SearchTooltipItemId.text(),
GuiText.SearchTooltipTag.text()));
GuiText.SearchTooltipTag.text(),
GuiText.SearchTooltipToolTips.text(),
GuiText.SearchTooltipItemId.text()));

// Sync the search text both ways but make the direction depend on which search has the focus
if (config.isSyncWithExternalSearch()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ public class TerminalSettingsScreen<C extends MEStorageMenu> extends AESubScreen
private final AECheckbox autoFocusCheckbox;
private final AECheckbox syncWithExternalCheckbox;
private final AECheckbox clearExternalCheckbox;
private final AECheckbox searchTooltipsCheckbox;

public TerminalSettingsScreen(MEStorageScreen<C> parent) {
super(parent, "/screens/terminals/terminal_settings.json");
Expand Down Expand Up @@ -57,8 +56,6 @@ public TerminalSettingsScreen(MEStorageScreen<C> parent) {
useExternalSearchRadio.setRadio(true);
useExternalSearchRadio.active = hasExternalSearch;

searchTooltipsCheckbox = widgets.addCheckbox("searchTooltipsCheckbox",
GuiText.SearchSettingsSearchTooltips.text(), this::save);
rememberCheckbox = widgets.addCheckbox("rememberCheckbox", GuiText.SearchSettingsRememberSearch.text(),
this::save);
autoFocusCheckbox = widgets.addCheckbox("autoFocusCheckbox", GuiText.SearchSettingsAutoFocus.text(),
Expand Down Expand Up @@ -109,7 +106,6 @@ private void updateState() {
autoFocusCheckbox.setSelected(config.isAutoFocusSearch());
syncWithExternalCheckbox.setSelected(config.isSyncWithExternalSearch());
clearExternalCheckbox.setSelected(config.isClearExternalSearchOnOpen());
searchTooltipsCheckbox.setSelected(config.isSearchTooltips());

rememberCheckbox.visible = useInternalSearchRadio.isSelected();
autoFocusCheckbox.visible = useInternalSearchRadio.isSelected();
Expand All @@ -124,7 +120,6 @@ private void save() {
config.setAutoFocusSearch(autoFocusCheckbox.isSelected());
config.setSyncWithExternalSearch(syncWithExternalCheckbox.isSelected());
config.setClearExternalSearchOnOpen(clearExternalCheckbox.isSelected());
config.setSearchTooltips(searchTooltipsCheckbox.isSelected());
config.setPinAutoCraftedItems(pinAutoCraftedItemsCheckbox.isSelected());
config.setNotifyForFinishedCraftingJobs(notifyForFinishedCraftingJobsCheckbox.isSelected());
config.setClearGridOnClose(clearGridOnCloseCheckbox.isSelected());
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/appeng/client/gui/me/search/AndSearchPredicate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package appeng.client.gui.me.search;

import java.util.List;
import java.util.function.Predicate;

import appeng.menu.me.common.GridInventoryEntry;

final class AndSearchPredicate implements Predicate<GridInventoryEntry> {
private final List<Predicate<GridInventoryEntry>> terms;

private AndSearchPredicate(List<Predicate<GridInventoryEntry>> terms) {
this.terms = terms;
}

public static Predicate<GridInventoryEntry> of(List<Predicate<GridInventoryEntry>> predicates) {
if (predicates.isEmpty()) {
return t -> true;
}
if (predicates.size() == 1) {
return predicates.getFirst();
}
return new AndSearchPredicate(predicates);
}

@Override
public boolean test(GridInventoryEntry entry) {
for (var term : terms) {
if (!term.test(entry)) {
return false;
}
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package appeng.client.gui.me.search;

import java.util.Locale;
import java.util.Objects;
import java.util.function.Predicate;

import appeng.api.stacks.AEKey;
import appeng.menu.me.common.GridInventoryEntry;

final class ItemIdSearchPredicate implements Predicate<GridInventoryEntry> {
private final String term;

public ItemIdSearchPredicate(String term) {
this.term = term.toLowerCase();
}

@Override
public boolean test(GridInventoryEntry gridInventoryEntry) {
AEKey what = Objects.requireNonNull(gridInventoryEntry.getWhat());
var id = what.getId().toString();
return id.toLowerCase(Locale.ROOT).contains(term);
}
}
39 changes: 39 additions & 0 deletions src/main/java/appeng/client/gui/me/search/ModSearchPredicate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package appeng.client.gui.me.search;

import java.util.Locale;
import java.util.Objects;
import java.util.function.Predicate;

import appeng.api.stacks.AEKey;
import appeng.menu.me.common.GridInventoryEntry;
import appeng.util.Platform;

final class ModSearchPredicate implements Predicate<GridInventoryEntry> {
private final String term;

public ModSearchPredicate(String term) {
this.term = normalize(term);
}

@Override
public boolean test(GridInventoryEntry gridInventoryEntry) {
AEKey entryInfo = Objects.requireNonNull(gridInventoryEntry.getWhat());
String modId = entryInfo.getModId();

if (modId != null) {
if (modId.contains(term)) {
return true;
}

String modName = Platform.getModName(modId);
modName = normalize(modName);
return modName.contains(term);
}

return false;
}

private static String normalize(String input) {
return input.toLowerCase(Locale.ROOT);
}
}
22 changes: 22 additions & 0 deletions src/main/java/appeng/client/gui/me/search/NameSearchPredicate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package appeng.client.gui.me.search;

import java.util.Objects;
import java.util.function.Predicate;

import appeng.api.stacks.AEKey;
import appeng.menu.me.common.GridInventoryEntry;

final class NameSearchPredicate implements Predicate<GridInventoryEntry> {
private final String term;

public NameSearchPredicate(String term) {
this.term = term.toLowerCase();
}

@Override
public boolean test(GridInventoryEntry gridInventoryEntry) {
AEKey entryInfo = Objects.requireNonNull(gridInventoryEntry.getWhat());
String displayName = entryInfo.getDisplayName().getString();
return displayName.toLowerCase().contains(term);
}
}
35 changes: 35 additions & 0 deletions src/main/java/appeng/client/gui/me/search/OrSearchPredicate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package appeng.client.gui.me.search;

import java.util.List;
import java.util.function.Predicate;

import appeng.menu.me.common.GridInventoryEntry;

final class OrSearchPredicate implements Predicate<GridInventoryEntry> {
private final List<Predicate<GridInventoryEntry>> terms;

private OrSearchPredicate(List<Predicate<GridInventoryEntry>> terms) {
this.terms = terms;
}

public static Predicate<GridInventoryEntry> of(List<Predicate<GridInventoryEntry>> filters) {
if (filters.isEmpty()) {
return t -> false;
}
if (filters.size() == 1) {
return filters.getFirst();
}
return new OrSearchPredicate(filters);
}

@Override
public boolean test(GridInventoryEntry entry) {
for (var term : terms) {
if (term.test(entry)) {
return true;
}
}

return false;
}
}
91 changes: 43 additions & 48 deletions src/main/java/appeng/client/gui/me/search/RepoSearch.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
package appeng.client.gui.me.search;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.WeakHashMap;
import java.util.function.Predicate;

import net.minecraft.ChatFormatting;

import it.unimi.dsi.fastutil.longs.Long2BooleanMap;
import it.unimi.dsi.fastutil.longs.Long2BooleanOpenHashMap;

import appeng.api.client.AEKeyRendering;
import appeng.api.stacks.AEKey;
import appeng.core.AEConfig;
import appeng.menu.me.common.GridInventoryEntry;
import appeng.util.Platform;

public class RepoSearch {

Expand All @@ -24,8 +19,7 @@ public class RepoSearch {
// Cached information
private final Long2BooleanMap cache = new Long2BooleanOpenHashMap();
private Predicate<GridInventoryEntry> search = (e) -> true;

private final Map<AEKey, String> tooltipCache = new WeakHashMap<>();
final Map<AEKey, String> tooltipCache = new WeakHashMap<>();

public RepoSearch() {
}
Expand All @@ -36,7 +30,7 @@ public String getSearchString() {

public void setSearchString(String searchString) {
if (!searchString.equals(this.searchString)) {
this.search = SearchPredicates.fromString(searchString, this);
this.search = fromString(searchString);
this.searchString = searchString;
this.cache.clear();
}
Expand All @@ -46,46 +40,47 @@ public boolean matches(GridInventoryEntry entry) {
return cache.computeIfAbsent(entry.getSerial(), s -> search.test(entry));
}

/**
* Gets the concatenated text of a keys tooltip for search purposes.
/*
* Creates a predicate for provided search string.
*/
public String getTooltipText(AEKey what) {
return tooltipCache.computeIfAbsent(what, key -> {
var lines = AEKeyRendering.getTooltip(key);

var tooltipText = new StringBuilder();
for (int i = 0; i < lines.size(); i++) {
var line = lines.get(i);

// Process last line and skip mod name if our heuristic detects it
if (i > 0 && i >= lines.size() - 1 && !AEConfig.instance().isSearchModNameInTooltips()) {
var text = line.getString();
boolean hadFormatting = false;
if (text.indexOf(ChatFormatting.PREFIX_CODE) != -1) {
text = ChatFormatting.stripFormatting(text);
hadFormatting = true;
} else {
hadFormatting = !line.getStyle().isEmpty();
}

if (!hadFormatting || !Objects.equals(text, Platform.getModName(what.getModId()))) {
tooltipText.append('\n').append(text);
}
} else {
if (i > 0) {
tooltipText.append('\n');
}
line.visit(text -> {
if (text.indexOf(ChatFormatting.PREFIX_CODE) != -1) {
text = ChatFormatting.stripFormatting(text);
}
tooltipText.append(text);
return Optional.empty();
});
}
private Predicate<GridInventoryEntry> fromString(String searchString) {
var orParts = searchString.split("\\|");

if (orParts.length == 1) {
return AndSearchPredicate.of(getPredicates(orParts[0]));
} else {
var orPartFilters = new ArrayList<Predicate<GridInventoryEntry>>(orParts.length);

for (String orPart : orParts) {
orPartFilters.add(AndSearchPredicate.of(getPredicates(orPart)));
}

return tooltipText.toString();
});
return OrSearchPredicate.of(orPartFilters);
}
}

/*
* Created as a helper function for {@code fromString()}. This is designed to handle between the | (or operations)
* to and the searched together delimited by " " Each space in {@code query} treated as a separate 'and' operation.
*/
private List<Predicate<GridInventoryEntry>> getPredicates(String query) {
var terms = query.toLowerCase().trim().split("\\s+");
var predicateFilters = new ArrayList<Predicate<GridInventoryEntry>>(terms.length);

for (String part : terms) {
if (part.startsWith("@")) {
predicateFilters.add(new ModSearchPredicate(part.substring(1)));
} else if (part.startsWith("#")) {
predicateFilters.add(new TooltipsSearchPredicate(part.substring(1), tooltipCache));
} else if (part.startsWith("$")) {
predicateFilters.add(new TagSearchPredicate(part.substring(1)));
} else if (part.startsWith("*")) {
predicateFilters.add(new ItemIdSearchPredicate(part.substring(1)));
} else {
predicateFilters.add(new NameSearchPredicate(part));
}
}

return predicateFilters;
}
}
Loading

0 comments on commit 43a1e29

Please sign in to comment.