From 05069aa62b09f02a8cd6e526ec58a30347a56500 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Wed, 27 Jul 2022 23:25:27 +0800 Subject: WIP Module --- .../shedaniel/rei/impl/client/search/IntRange.java | 27 ++ .../rei/impl/client/search/SearchProviderImpl.java | 129 ++++++++ .../search/argument/AlternativeArgument.java | 74 +++++ .../rei/impl/client/search/argument/Argument.java | 360 +++++++++++++++++++++ .../client/search/argument/CompoundArgument.java | 88 +++++ .../rei/impl/client/search/argument/IndexSet.java | 173 ++++++++++ .../client/search/argument/InputMethodMatcher.java | 129 ++++++++ .../argument/type/AlwaysMatchingArgumentType.java | 60 ++++ .../client/search/argument/type/ArgumentType.java | 101 ++++++ .../argument/type/ArgumentTypesRegistry.java | 52 +++ .../argument/type/IdentifierArgumentType.java | 88 +++++ .../client/search/argument/type/MatchType.java | 41 +++ .../search/argument/type/ModArgumentType.java | 103 ++++++ .../search/argument/type/RegexArgumentType.java | 99 ++++++ .../search/argument/type/TagArgumentType.java | 92 ++++++ .../search/argument/type/TextArgumentType.java | 75 +++++ .../search/argument/type/TooltipArgumentType.java | 123 +++++++ .../client/search/method/DefaultInputMethod.java | 76 +++++ .../search/method/InputMethodRegistryImpl.java | 110 +++++++ .../search/result/ArgumentApplicableResult.java | 122 +++++++ .../client/runtime/SearchFilterPrepareWatcher.java | 91 ++++++ 21 files changed, 2213 insertions(+) create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/IntRange.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/IndexSet.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/InputMethodMatcher.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/AlwaysMatchingArgumentType.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ArgumentType.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ArgumentTypesRegistry.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/IdentifierArgumentType.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/MatchType.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ModArgumentType.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/RegexArgumentType.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TagArgumentType.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TextArgumentType.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/method/DefaultInputMethod.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/method/InputMethodRegistryImpl.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/result/ArgumentApplicableResult.java create mode 100644 runtime-engine/search/src/main/java/me/shedaniel/rei/plugin/client/runtime/SearchFilterPrepareWatcher.java (limited to 'runtime-engine/search/src/main/java') diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/IntRange.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/IntRange.java new file mode 100644 index 000000000..94c259b8c --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/IntRange.java @@ -0,0 +1,27 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search; + +public record IntRange(int min, int max) { +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java new file mode 100644 index 000000000..894153ddc --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java @@ -0,0 +1,129 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search; + +import com.google.common.base.Suppliers; +import me.shedaniel.rei.api.client.search.SearchFilter; +import me.shedaniel.rei.api.client.search.SearchProvider; +import me.shedaniel.rei.api.client.search.method.InputMethod; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.impl.client.search.argument.AlternativeArgument; +import me.shedaniel.rei.impl.client.search.argument.Argument; +import me.shedaniel.rei.impl.client.search.argument.CompoundArgument; +import me.shedaniel.rei.impl.client.search.argument.type.ArgumentType; +import me.shedaniel.rei.impl.client.util.CrashReportUtils; +import me.shedaniel.rei.impl.common.InternalLogger; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class SearchProviderImpl implements SearchProvider { + @Override + public void startReload() { + } + + @Override + public SearchFilter createFilter(String filter, InputMethod inputMethod) { + return new SearchFilterImpl(filter, inputMethod); + } + + @Override + public void clearCache() { + Argument.SEARCH_CACHE.clear(); + } + + @Override + public boolean hasCache() { + return !Argument.SEARCH_CACHE.isEmpty(); + } + + public static class SearchFilterImpl implements SearchFilter { + private final String filter; + private final InputMethod inputMethod; + private final Supplier> arguments; + private final Supplier>> argumentTypes; + + public SearchFilterImpl(String filter, InputMethod inputMethod) { + this.filter = filter; + this.inputMethod = inputMethod; + this.arguments = Suppliers.memoize(() -> Argument.bakeArguments(filter)); + this.argumentTypes = Suppliers.memoize(() -> this.arguments.get().stream() + .flatMap(CompoundArgument::stream) + .flatMap(AlternativeArgument::stream) + .map(Argument::getArgument) + .distinct() + .collect(Collectors.toList())); + InternalLogger.getInstance().debug("Created search filter with %s using %s", filter, inputMethod.getName().getString()); + } + + @Override + public boolean test(EntryStack stack) { + try { + return Argument.matches(stack, arguments.get(), inputMethod); + } catch (Throwable throwable) { + CrashReport report = CrashReportUtils.essential(throwable, "Testing entry with search filter"); + CrashReportCategory category = report.addCategory("Search entry details"); + try { + stack.fillCrashReport(report, category); + } catch (Throwable throwable1) { + category.setDetailError("Filling Report", throwable1); + } + throw CrashReportUtils.throwReport(report); + } + } + + @Override + public void prepareFilter(Collection> stacks) { + Argument.prepareFilter(stacks, argumentTypes.get()); + } + + @Override + public void processDecoration(ParseDecorationSink sink) { + Argument.bakeArguments(filter, sink); + } + + @Override + public String getFilter() { + return filter; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SearchFilterImpl that = (SearchFilterImpl) o; + return Objects.equals(filter, that.filter); + } + + @Override + public int hashCode() { + return Objects.hash(filter); + } + } +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java new file mode 100644 index 000000000..01b169f6b --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java @@ -0,0 +1,74 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search.argument; + +import com.google.common.collect.ForwardingList; +import org.jetbrains.annotations.ApiStatus; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@ApiStatus.Internal +public class AlternativeArgument extends ForwardingList> { + static final AlternativeArgument EMPTY = new AlternativeArgument(Collections.emptyList()); + + private final List> arguments; + + public AlternativeArgument(List> arguments) { + this.arguments = arguments; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + protected List> delegate() { + return arguments; + } + + public static class Builder { + private List> arguments; + + public Builder add(Argument argument) { + if (arguments == null) { + this.arguments = new ArrayList<>(); + } + + arguments.add(argument); + return this; + } + + public boolean isEmpty() { + return arguments == null; + } + + public AlternativeArgument build() { + if (arguments == null) return AlternativeArgument.EMPTY; + if (arguments.size() == 1) return new AlternativeArgument(Collections.singletonList(arguments.get(0))); + return new AlternativeArgument(arguments); + } + } +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java new file mode 100644 index 000000000..8724af7d1 --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java @@ -0,0 +1,360 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search.argument; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.IntIntMutablePair; +import it.unimi.dsi.fastutil.ints.IntIntPair; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import it.unimi.dsi.fastutil.shorts.Short2ObjectMaps; +import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; +import me.shedaniel.rei.api.client.config.ConfigObject; +import me.shedaniel.rei.api.client.gui.config.SearchMode; +import me.shedaniel.rei.api.client.search.SearchFilter; +import me.shedaniel.rei.api.client.search.method.CharacterUnpackingInputMethod; +import me.shedaniel.rei.api.client.search.method.InputMethod; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.util.CollectionUtils; +import me.shedaniel.rei.api.common.util.EntryStacks; +import me.shedaniel.rei.impl.client.search.argument.type.ArgumentType; +import me.shedaniel.rei.impl.client.search.argument.type.ArgumentTypesRegistry; +import me.shedaniel.rei.impl.client.search.result.ArgumentApplicableResult; +import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@ApiStatus.Internal +@Environment(EnvType.CLIENT) +public class Argument { + public static final Short2ObjectMap> SEARCH_CACHE = Short2ObjectMaps.synchronize(new Short2ObjectOpenHashMap<>()); + private static final Object NO_CACHE = new Object(); + private static final AtomicReference lastLanguage = new AtomicReference<>(); + private ArgumentType argumentType; + private String text; + private T filterData; + private boolean regular; + private final int start; + private final int end; + private static final Pattern SPLIT_PATTERN = Pattern.compile("(?:\"([^\"]*)\")|([^\\s]+)"); + + public Argument(ArgumentType argumentType, String text, boolean regular, int start, int end, boolean lowercase) { + this.argumentType = argumentType; + this.text = lowercase ? text.toLowerCase(Locale.ROOT) : text; + this.regular = regular; + this.filterData = null; + this.start = start; + this.end = end; + } + + public int start() { + return start; + } + + public int end() { + return end; + } + + public static List bakeArguments(String filter) { + return bakeArguments(filter, null); + } + + public static List bakeArguments(String filter, @Nullable SearchFilter.ParseDecorationSink sink) { + List compoundArguments = Lists.newArrayList(); + int tokenStartIndex = 0; + + for (String token : StringUtils.splitByWholeSeparatorPreserveAllTokens(filter, "|")) { + Matcher terms = SPLIT_PATTERN.matcher(token); + CompoundArgument.Builder builder = CompoundArgument.builder(); + while (terms.find()) { + AlternativeArgument.Builder alternativeBuilder = AlternativeArgument.builder(); + + for (ArgumentType type : ArgumentTypesRegistry.ARGUMENT_TYPE_LIST) { + applyArgument(type, filter, terms, tokenStartIndex, alternativeBuilder, true, sink); + if (!alternativeBuilder.isEmpty()) { + break; + } + } + + if (alternativeBuilder.isEmpty()) { + for (ArgumentType type : ArgumentTypesRegistry.ARGUMENT_TYPE_LIST) { + applyArgument(type, filter, terms, tokenStartIndex, alternativeBuilder, false, sink); + } + } + + builder.add(alternativeBuilder); + } + compoundArguments.add(builder.build()); + tokenStartIndex += 1 + token.length(); + if (sink != null && tokenStartIndex - 1 < filter.length()) { + sink.addSplitter(tokenStartIndex - 1); + } + } + prepareSearchFilter(compoundArguments); + return compoundArguments; + } + + private static void prepareSearchFilter(List compoundArguments) { + for (CompoundArgument arguments : compoundArguments) { + for (AlternativeArgument alternativeArgument : arguments) { + for (Argument argument : alternativeArgument) { + //noinspection RedundantCast + ((Argument) argument).filterData = argument.argumentType.prepareSearchFilter(argument.getText()); + } + } + } + } + + private static void applyArgument(ArgumentType type, String filter, Matcher terms, int tokenStartIndex, AlternativeArgument.Builder alternativeBuilder, boolean forceGrammar, @Nullable SearchFilter.ParseDecorationSink sink) { + String term = MoreObjects.firstNonNull(terms.group(1), terms.group(2)); + if (type.getSearchMode() == SearchMode.NEVER) return; + ArgumentApplicableResult result = type.checkApplicable(term, forceGrammar); + + if (result.isApplicable()) { + int group = terms.group(1) != null ? 1 : 2; + Argument argument = new Argument<>(type, result.getText(), !result.isInverted(), + terms.start(group) + tokenStartIndex, terms.end(group) + tokenStartIndex, !result.shouldPreserveCasing()); + alternativeBuilder.add(argument); + if (sink != null) { + if (group == 1) { + sink.addQuote(terms.start() + tokenStartIndex); + if (terms.end() - 1 + tokenStartIndex < filter.length()) { + sink.addQuote(terms.end() - 1 + tokenStartIndex); + } + } + sink.addPart(IntIntPair.of(argument.start(), argument.end()), argument.argumentType.getHighlightedStyle(), result.isUsingGrammar(), + CollectionUtils.map(result.grammarRanges(), range -> IntIntPair.of(range.min(), range.max())), terms.start() + tokenStartIndex); + } + } + } + + public static boolean matches(EntryStack stack, List compoundArguments, InputMethod inputMethod) { + if (compoundArguments.isEmpty()) return true; + String newLanguage = Minecraft.getInstance().options.languageCode; + if (!Objects.equals(lastLanguage.getAndSet(newLanguage), newLanguage)) { + SEARCH_CACHE.clear(); + } + + a: + for (CompoundArgument arguments : compoundArguments) { + for (AlternativeArgument argument : arguments) { + if (!matches(stack, argument, inputMethod)) { + continue a; + } + } + + return true; + } + + return false; + } + + private static boolean matches(EntryStack stack, AlternativeArgument alternativeArgument, InputMethod inputMethod) { + if (alternativeArgument.isEmpty()) return true; + long hashExact = EntryStacks.hashExact(stack); + ResultSinkImpl sink = new ResultSinkImpl<>(inputMethod); + + for (Argument argument : alternativeArgument) { + sink.filters = inputMethod.expendFilter(argument.getText()); + if (matches(argument.getArgument(), stack, hashExact, argument.filterData, sink) == argument.isRegular()) { + return true; + } + } + + return false; + } + + private static Long2ObjectMap getSearchCache(ArgumentType argumentType) { + short argumentIndex = (short) argumentType.getIndex(); + Long2ObjectMap map = SEARCH_CACHE.get(argumentIndex); + if (map == null) { + SEARCH_CACHE.put(argumentIndex, map = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>())); + } + return map; + } + + private static boolean matches(ArgumentType argumentType, EntryStack stack, long hashExact, R filterData, ResultSinkImpl sink) { + Long2ObjectMap map = getSearchCache(argumentType); + Object value = map.get(hashExact); + if (value == null) { + value = argumentType.cacheData(stack); + map.put(hashExact, value == null ? NO_CACHE : value); + } + sink.matches = false; + argumentType.matches(value == NO_CACHE ? null : (B) value, stack, (T) filterData, sink); + return sink.matches; + } + + private static class ResultSinkImpl implements ArgumentType.ResultSink { + private final InputMethod inputMethod; + private boolean matches; + private Iterable filters; + + public ResultSinkImpl(InputMethod inputMethod) { + this.inputMethod = inputMethod; + } + + @Override + public boolean testTrue() { + return matches = true; + } + + @Override + public boolean testString(String text) { + if (matches) return true; + if (inputMethod instanceof CharacterUnpackingInputMethod im) { + for (T filter : filters) { + if (InputMethodMatcher.contains(im, IntList.of(text.codePoints().toArray()), (IntList) filter)) { + return matches = true; + } + } + } else { + for (T filter : filters) { + if (inputMethod.contains(text, filter)) { + return matches = true; + } + } + } + return false; + } + } + + public static Long prepareStart = null; + public static Collection> prepareStacks = null; + public static IntIntPair prepareStage = null; + public static IntIntPair[] currentStages = null; + + public static void prepareFilter(Collection> stacks, Collection> argumentTypes) { + if (prepareStage != null || currentStages != null) return; + try { + prepareStart = Util.getEpochMillis(); + prepareStacks = stacks; + prepareStage = new IntIntMutablePair(0, argumentTypes.size()); + currentStages = new IntIntPair[argumentTypes.size()]; + List hashedStacks = CollectionUtils.map(stacks, HashedEntryStackWrapper::new); + int searchPartitionSize = ConfigObject.getInstance().getAsyncSearchPartitionSize(); + boolean async = ConfigObject.getInstance().shouldAsyncSearch() && stacks.size() > searchPartitionSize * 4; + List>> futures = Lists.newArrayList(); + List, CompletableFuture>>> pairs = Lists.newArrayList(); + + for (ArgumentType argumentType : argumentTypes) { + prepareStage.first(prepareStage.firstInt() + 1); + Long2ObjectMap map = getSearchCache(argumentType); + IntIntPair currentStage = currentStages[prepareStage.firstInt() - 1] = new IntIntMutablePair(0, hashedStacks.size()); + + if (async) { + for (Collection partitionStacks : CollectionUtils.partition(hashedStacks, searchPartitionSize)) { + CompletableFuture> future = CompletableFuture.supplyAsync(() -> { + Long2ObjectMap out = new Long2ObjectArrayMap<>(searchPartitionSize + 1); + for (HashedEntryStackWrapper stack : partitionStacks) { + if (map.get(stack.hashExact()) == null) { + Object data = argumentType.cacheData(stack.unwrap()); + + if (data != null) { + out.put(stack.hashExact(), data); + } + } + } + return out; + }).whenComplete((objectLong2ObjectMap, throwable) -> { + currentStage.first(currentStage.firstInt() + partitionStacks.size()); + }); + futures.add(future); + pairs.add(Pair.of(argumentType, future)); + } + } else { + for (HashedEntryStackWrapper stack : hashedStacks) { + currentStage.first(currentStage.firstInt() + 1); + + if (map.get(stack.hashExact()) == null) { + Object data = argumentType.cacheData(stack.unwrap()); + + if (data != null) { + map.put(stack.hashExact(), data); + } + } + } + } + } + + if (async) { + try { + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(30, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + e.printStackTrace(); + } + for (Pair, CompletableFuture>> pair : pairs) { + Long2ObjectMap now = pair.second().getNow(null); + if (now != null) getSearchCache(pair.left()).putAll(now); + } + } + } finally { + prepareStart = null; + prepareStacks = null; + prepareStage = null; + currentStages = null; + } + } + + public ArgumentType getArgument() { + return argumentType; + } + + public String getText() { + return text; + } + + public boolean isRegular() { + return regular; + } + + @Override + public String toString() { + return String.format("Argument[%s]: name = %s, regular = %b", argumentType.getName(), text, regular); + } + +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java new file mode 100644 index 000000000..bcb3c0bf3 --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java @@ -0,0 +1,88 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search.argument; + +import com.google.common.collect.ForwardingList; +import org.jetbrains.annotations.ApiStatus; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@ApiStatus.Internal +public class CompoundArgument extends ForwardingList { + public static final CompoundArgument ALWAYS = new CompoundArgument(AlternativeArgument.EMPTY); + private final AlternativeArgument[] arguments; + private final List argumentList; + + private CompoundArgument(AlternativeArgument... arguments) { + this.arguments = arguments; + this.argumentList = Arrays.asList(arguments); + } + + public static CompoundArgument of(AlternativeArgument... arguments) { + if (arguments.length == 0) return ALWAYS; + return new CompoundArgument(arguments); + } + + public static Builder builder() { + return new Builder(); + } + + public final boolean isAlways() { + return this == ALWAYS; + } + + @Override + protected List delegate() { + return argumentList; + } + + public static class Builder { + private List arguments; + + public Builder add(Argument argument) { + return add(new AlternativeArgument(Collections.singletonList(argument))); + } + + public Builder add(AlternativeArgument.Builder builder) { + return add(builder.build()); + } + + public Builder add(AlternativeArgument argument) { + if (arguments == null) { + this.arguments = new ArrayList<>(); + } + + arguments.add(argument); + return this; + } + + public CompoundArgument build() { + if (arguments == null) return CompoundArgument.ALWAYS; + return CompoundArgument.of(arguments.toArray(new AlternativeArgument[0])); + } + } +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/IndexSet.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/IndexSet.java new file mode 100644 index 000000000..025063d62 --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/IndexSet.java @@ -0,0 +1,173 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search.argument; + +import java.util.function.IntConsumer; +import java.util.function.IntPredicate; + +/** + * MIT License + *

+ * Copyright (c) 2019 Juntong Liu + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +public class IndexSet { + public static final IndexSet ZERO = new IndexSet(0x1); + public static final IndexSet ONE = new IndexSet(0x2); + public static final IndexSet NONE = new IndexSet(0x0); + + int value = 0x0; + + public IndexSet() { + } + + public IndexSet(IndexSet set) { + value = set.value; + } + + public IndexSet(int value) { + this.value = value; + } + + public void set(int index) { + int i = 0x1 << index; + value |= i; + } + + public boolean get(int index) { + int i = 0x1 << index; + return (value & i) != 0; + } + + public void merge(IndexSet s) { + value = value == 0x1 ? s.value : (value |= s.value); + } + + public boolean traverse(IntPredicate p) { + int v = value; + for (int i = 0; i < 7; i++) { + if ((v & 0x1) == 0x1 && !p.test(i)) return false; + else if (v == 0) return true; + v >>= 1; + } + return true; + } + + public void foreach(IntConsumer c) { + int v = value; + for (int i = 0; i < 7; i++) { + if ((v & 0x1) == 0x1) c.accept(i); + else if (v == 0) return; + v >>= 1; + } + } + + public void offset(int i) { + value <<= i; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + traverse(i -> { + builder.append(i); + builder.append(", "); + return true; + }); + if (builder.length() != 0) { + builder.delete(builder.length() - 2, builder.length()); + return builder.toString(); + } else return "0"; + } + + public boolean isEmpty() { + return value == 0x0; + } + + public IndexSet copy() { + return new IndexSet(value); + } + + static class Immutable extends IndexSet { + @Override + public void set(int index) { + throw new UnsupportedOperationException("Immutable collection"); + } + + @Override + public void merge(IndexSet s) { + throw new UnsupportedOperationException("Immutable collection"); + } + + @Override + public void offset(int i) { + throw new UnsupportedOperationException("Immutable collection"); + } + } + + static class Storage { + IndexSet tmp = new Immutable(); + int[] data = new int[16]; + + public void set(IndexSet is, int index) { + if (index >= data.length) { + int size = index; + size |= size >> 1; + size |= size >> 2; + size |= size >> 4; + size |= size >> 8; + size |= size >> 16; + int[] replace = new int[size + 1]; + System.arraycopy(data, 0, replace, 0, data.length); + data = replace; + } + data[index] = is.value + 1; + } + + public IndexSet get(int index) { + if (index >= data.length) return null; + int ret = data[index]; + if (ret == 0) return null; + tmp.value = ret - 1; + return tmp; + } + } +} \ No newline at end of file diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/InputMethodMatcher.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/InputMethodMatcher.java new file mode 100644 index 000000000..58e5c46cd --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/InputMethodMatcher.java @@ -0,0 +1,129 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search.argument; + +import it.unimi.dsi.fastutil.ints.IntList; +import me.shedaniel.rei.api.client.search.method.CharacterUnpackingInputMethod; +import me.shedaniel.rei.api.client.search.method.CharacterUnpackingInputMethod.ExpendedChar; + +import java.util.List; + +/** + * MIT License + *

+ * Copyright (c) 2019 Juntong Liu + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +public class InputMethodMatcher { + public static boolean contains(CharacterUnpackingInputMethod inputMethod, IntList s1, IntList s2) { + if (!s1.isEmpty()) { + for (int i = 0; i < s1.size(); i++) + if (check(inputMethod, s1, i, s2, 0, true)) return true; + } + return false; + } + + public static boolean matches(CharacterUnpackingInputMethod inputMethod, IntList s1, IntList s2) { + if (s1.isEmpty()) return s2.isEmpty(); + else return check(inputMethod, s1, 0, s2, 0, false); + } + + private static IndexSet match(CharacterUnpackingInputMethod inputMethod, int self, IntList str, int start, boolean partial) { + List expendedList = inputMethod.expendSourceChar(self); + IndexSet ret = (str.getInt(start) == self ? IndexSet.ONE : IndexSet.NONE).copy(); + for (ExpendedChar integers : expendedList) { + ret.merge(match(integers, str, start, partial)); + } + return ret; + } + + private static IndexSet match(ExpendedChar phonemes, IntList str, int start, boolean partial) { + IndexSet active = IndexSet.ZERO; + IndexSet ret = new IndexSet(); + for (IntList phoneme : phonemes.phonemes()) { + active = matchPhoneme(List.of(phoneme), str, active, start, partial); + if (active.isEmpty()) return ret; + ret.merge(active); + } + return ret; + } + + private static IndexSet matchPhoneme(List strs, IntList source, IndexSet idx, int start, boolean partial) { + if (strs.size() == 1 && strs.get(0).isEmpty()) return new IndexSet(idx); + IndexSet ret = new IndexSet(); + idx.foreach(i -> { + IndexSet is = matchM(strs, source, start + i, partial); + is.offset(i); + ret.merge(is); + }); + return ret; + } + + public static IndexSet matchM(List strs, IntList source, int start, boolean partial) { + IndexSet ret = new IndexSet(); + if (strs.size() == 1 && strs.get(0).isEmpty()) return ret; + for (IntList str : strs) { + int size = strCmp(source, str, start); + if (partial && start + size == source.size()) ret.set(size); // ending match + else if (size == str.size()) ret.set(size); // full match + } + return ret; + } + + private static int strCmp(IntList a, IntList b, int aStart) { + int len = Math.min(a.size() - aStart, b.size()); + for (int i = 0; i < len; i++) + if (a.getInt(i + aStart) != b.getInt(i)) return i; + return len; + } + + private static boolean check(CharacterUnpackingInputMethod inputMethod, IntList s1, int start1, IntList s2, int start2, boolean partial) { + if (start2 == s2.size()) return partial || start1 == s1.size(); + + int ch = s1.getInt(start1); + IndexSet s = match(inputMethod, ch, s2, start2, partial); + + if (start1 == s1.size() - 1) { + int i = s2.size() - start2; + return s.get(i); + } else return !s.traverse(i -> !check(inputMethod, s1, start1 + 1, s2, start2 + i, partial)); + } +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/AlwaysMatchingArgumentType.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/AlwaysMatchingArgumentType.java new file mode 100644 index 000000000..f69a1c0b8 --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/AlwaysMatchingArgumentType.java @@ -0,0 +1,60 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search.argument.type; + +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.impl.client.search.result.ArgumentApplicableResult; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.util.Unit; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +@Environment(EnvType.CLIENT) +public final class AlwaysMatchingArgumentType extends ArgumentType { + @Override + public String getName() { + return "always"; + } + + @Override + public Unit cacheData(EntryStack stack) { + return null; + } + + @Override + public void matches(Unit data, EntryStack stack, Unit filterData, ResultSink sink) { + sink.testTrue(); + } + + @Override + public Unit prepareSearchFilter(String searchText) { + return Unit.INSTANCE; + } + + @Override + public ArgumentApplicableResult checkApplicable(String text, boolean forceGrammar) { + return ArgumentApplicableResult.notApplicable(); + } +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ArgumentType.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ArgumentType.java new file mode 100644 index 000000000..de06b2167 --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ArgumentType.java @@ -0,0 +1,101 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search.argument.type; + +import me.shedaniel.rei.api.client.gui.config.SearchMode; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.impl.client.search.result.ArgumentApplicableResult; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.network.chat.Style; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +@Environment(EnvType.CLIENT) +public abstract class ArgumentType { + private int index = -1; + + public ArgumentType() { + } + + public abstract String getName(); + + @Nullable + public String getPrefix() { + return null; + } + + public Style getHighlightedStyle() { + return Style.EMPTY; + } + + public SearchMode getSearchMode() { + return SearchMode.PREFIX; + } + + public ArgumentApplicableResult checkApplicable(String text, boolean forceGrammar) { + String prefix = getPrefix(); + if (forceGrammar && !prefix.isEmpty()) { + ArgumentApplicableResult status = checkApplicable(text, prefix); + if (status.isApplicable()) { + return status; + } + } + if (!forceGrammar && getSearchMode() == SearchMode.ALWAYS) { + ArgumentApplicableResult status = checkApplicable(text, ""); + if (status.isApplicable()) { + status.notUsingGrammar(); + } + return status; + } + return ArgumentApplicableResult.notApplicable(); + } + + private ArgumentApplicableResult checkApplicable(String text, String prefix) { + if (prefix == null) return ArgumentApplicableResult.notApplicable(); + if (text.startsWith("-" + prefix)) return ArgumentApplicableResult.applyInverted(text.substring(1 + prefix.length())).grammar(0, prefix.length() + 1); + if (!prefix.isEmpty() && text.startsWith(prefix + "-")) + return ArgumentApplicableResult.applyInverted(text.substring(1 + prefix.length())).grammar(0, prefix.length() + 1); + if (text.startsWith(prefix)) return ArgumentApplicableResult.apply(text.substring(prefix.length())).grammar(0, prefix.length()); + return ArgumentApplicableResult.notApplicable(); + } + + public abstract R cacheData(EntryStack stack); + + public abstract T prepareSearchFilter(String searchText); + + public abstract void matches(R data, EntryStack stack, T filterData, ResultSink sink); + + public int getIndex() { + if (index >= 0) return index; + return index = ArgumentTypesRegistry.ARGUMENT_TYPE_LIST.indexOf(this); + } + + public interface ResultSink { + boolean testTrue(); + + boolean testString(String text); + } +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ArgumentTypesRegistry.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ArgumentTypesRegistry.java new file mode 100644 index 000000000..03bddf378 --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ArgumentTypesRegistry.java @@ -0,0 +1,52 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search.argument.type; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.jetbrains.annotations.ApiStatus; + +import java.util.List; +import java.util.Map; + +@ApiStatus.Internal +public final class ArgumentTypesRegistry { + public static final Map> ARGUMENT_TYPES = Maps.newHashMap(); + public static final List> ARGUMENT_TYPE_LIST = Lists.newArrayList(); + + static { + register(new AlwaysMatchingArgumentType()); + register(new ModArgumentType()); + register(new TooltipArgumentType()); + register(new TagArgumentType()); + register(new IdentifierArgumentType()); + register(new RegexArgumentType()); + register(new TextArgumentType()); + } + + private static void register(ArgumentType argumentType) { + ARGUMENT_TYPES.put(argumentType.getName(), argumentType); + ARGUMENT_TYPE_LIST.add(argumentType); + } +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/IdentifierArgumentType.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/IdentifierArgumentType.java new file mode 100644 index 000000000..97a490124 --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/IdentifierArgumentType.java @@ -0,0 +1,88 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search.argument.type; + +import me.shedaniel.rei.api.client.config.ConfigObject; +import me.shedaniel.rei.api.client.gui.config.SearchMode; +import me.shedaniel.rei.api.common.entry.EntryStack; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.TextColor; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Unit; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +@Environment(EnvType.CLIENT) +public final class IdentifierArgumentType extends ArgumentType { + private static final String EMPTY = ""; + private static final Style STYLE = Style.EMPTY.withColor(TextColor.fromRgb(0x8d7eed)); + + @Override + public String getName() { + return "identifier"; + } + + @Override + @Nullable + public String getPrefix() { + return "*"; + } + + @Override + public Style getHighlightedStyle() { + return STYLE; + } + + @Override + public SearchMode getSearchMode() { + return ConfigObject.getInstance().getIdentifierSearchMode(); + } + + @Override + public String cacheData(EntryStack stack) { + ResourceLocation identifier = stack.getIdentifier(); + if (identifier != null) { + String s = identifier.getPath(); + if (!s.isEmpty()) { + return s; + } + } + return EMPTY; + } + + @Override + public void matches(String identifier, EntryStack stack, Unit filterData, ResultSink sink) { + if (!identifier.isEmpty()) { + sink.testString(identifier); + } + } + + @Override + public Unit prepareSearchFilter(String searchText) { + return Unit.INSTANCE; + } +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/MatchType.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/MatchType.java new file mode 100644 index 000000000..d1507fdaf --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/MatchType.java @@ -0,0 +1,41 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.search.argument.type; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public enum MatchType { + INVERT_MATCHED, + UNMATCHED, + MATCHED; + + public boolean isMatched() { + return this != UNMATCHED; + } + + public boolean isInverted() { + return this == INVERT_MATCHED; + } +} diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ModArgumentType.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ModArgumentType.java new file mode 100644 index 000000000..49d0d9e7e --- /dev/null +++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ModArgumentType.java @@ -0,0 +1,103 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the f