diff --git a/api/src/main/java/net/kyori/adventure/text/TextReplacementConfig.java b/api/src/main/java/net/kyori/adventure/text/TextReplacementConfig.java index 3d693999c..6563d0158 100644 --- a/api/src/main/java/net/kyori/adventure/text/TextReplacementConfig.java +++ b/api/src/main/java/net/kyori/adventure/text/TextReplacementConfig.java @@ -144,12 +144,26 @@ default Builder matchLiteral(final String literal) { /** * Set the function to determine how an individual match should be processed. * - * @param condition a function of {@code (index, replaced)} used to determine if matches should be replaced, where "replaced" is the number of successful replacements. + * @param condition a function of {@code (matchCount, replaced)} used to determine if matches should be replaced, where "matchCount" is the number of matches + * that have been found, including the current one, and "replaced" is the number of successful replacements. * @return this builder * @since 4.2.0 */ @Contract("_ -> this") - @NonNull Builder condition(final @NonNull IntFunction2 condition); + default @NonNull Builder condition(final @NonNull IntFunction2 condition) { + return this.condition((result, matchCount, replaced) -> condition.apply(matchCount, replaced)); + } + + /** + * Set the function to determine how an individual match should be processed. + * + * @param condition a function that determines whether a replacement should occur + * @return this builder + * @see Condition + * @since 4.8.0 + */ + @Contract("_ -> this") + @NonNull Builder condition(final @NonNull Condition condition); /* * ------------------------- @@ -207,4 +221,23 @@ default Builder matchLiteral(final String literal) { @Contract("_ -> this") @NonNull Builder replacement(final @NonNull BiFunction replacement); } + + /** + * A function determining whether a certain match should be replaced. + * + * @since 4.8.0 + */ + @FunctionalInterface + interface Condition { + /** + * Determine how a single match should be handled. + * + * @param result the current match result + * @param matchCount the number of matches encountered, including this one and matches that were not replaced + * @param replaced the number of matches that have already been replaced + * @return whether a certain match should + * @since 4.8.0 + */ + @NonNull PatternReplacementResult shouldReplace(final @NonNull MatchResult result, final int matchCount, final int replaced); + } } diff --git a/api/src/main/java/net/kyori/adventure/text/TextReplacementConfigImpl.java b/api/src/main/java/net/kyori/adventure/text/TextReplacementConfigImpl.java index 12b6b78d0..3a3ddff1b 100644 --- a/api/src/main/java/net/kyori/adventure/text/TextReplacementConfigImpl.java +++ b/api/src/main/java/net/kyori/adventure/text/TextReplacementConfigImpl.java @@ -27,7 +27,6 @@ import java.util.regex.MatchResult; import java.util.regex.Pattern; import java.util.stream.Stream; -import net.kyori.adventure.util.IntFunction2; import net.kyori.examination.ExaminableProperty; import net.kyori.examination.string.StringExaminer; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -39,7 +38,7 @@ final class TextReplacementConfigImpl implements TextReplacementConfig { private final Pattern matchPattern; private final BiFunction replacement; - private final IntFunction2 continuer; + private final Condition continuer; TextReplacementConfigImpl(final Builder builder) { this.matchPattern = builder.matchPattern; @@ -78,7 +77,7 @@ public String toString() { static final class Builder implements TextReplacementConfig.Builder { @MonotonicNonNull Pattern matchPattern; @MonotonicNonNull BiFunction replacement; - IntFunction2 continuer = (index, replacement) -> PatternReplacementResult.REPLACE; + TextReplacementConfig.Condition continuer = (matchResult, index, replacement) -> PatternReplacementResult.REPLACE; Builder() { } @@ -96,7 +95,7 @@ static final class Builder implements TextReplacementConfig.Builder { } @Override - public @NonNull Builder condition(final @NonNull IntFunction2 condition) { + public @NonNull Builder condition(final TextReplacementConfig.@NonNull Condition condition) { this.continuer = requireNonNull(condition, "continuation"); return this; } diff --git a/api/src/main/java/net/kyori/adventure/text/TextReplacementRenderer.java b/api/src/main/java/net/kyori/adventure/text/TextReplacementRenderer.java index ac724afe3..65a4d0e0d 100644 --- a/api/src/main/java/net/kyori/adventure/text/TextReplacementRenderer.java +++ b/api/src/main/java/net/kyori/adventure/text/TextReplacementRenderer.java @@ -31,7 +31,6 @@ import java.util.regex.Pattern; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.renderer.ComponentRenderer; -import net.kyori.adventure.util.IntFunction2; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -60,7 +59,7 @@ private TextReplacementRenderer() { final Matcher matcher = state.pattern.matcher(content); int replacedUntil = 0; // last index handled while(matcher.find()) { - final PatternReplacementResult result = state.continuer.apply(++state.matchCount, state.replaceCount); + final PatternReplacementResult result = state.continuer.shouldReplace(matcher, ++state.matchCount, state.replaceCount); if(result == PatternReplacementResult.CONTINUE) { // ignore this replacement continue; @@ -189,13 +188,13 @@ private TextReplacementRenderer() { static final class State { final Pattern pattern; final BiFunction replacement; - final IntFunction2 continuer; + final TextReplacementConfig.Condition continuer; boolean running = true; int matchCount = 0; int replaceCount = 0; boolean firstMatch = true; - State(final @NonNull Pattern pattern, final @NonNull BiFunction replacement, final @NonNull IntFunction2 continuer) { + State(final @NonNull Pattern pattern, final @NonNull BiFunction replacement, final TextReplacementConfig.@NonNull Condition continuer) { this.pattern = pattern; this.replacement = replacement; this.continuer = continuer; diff --git a/api/src/test/java/net/kyori/adventure/text/TextReplacementRendererTest.java b/api/src/test/java/net/kyori/adventure/text/TextReplacementRendererTest.java index 97cc7fe87..c2c546e77 100644 --- a/api/src/test/java/net/kyori/adventure/text/TextReplacementRendererTest.java +++ b/api/src/test/java/net/kyori/adventure/text/TextReplacementRendererTest.java @@ -218,13 +218,32 @@ void testReplaceAtStart() { void testReplaceAtStartReturnsChild() { final Component base = Component.text("value"); - final Component replaced = base.replaceText(c -> c.match("value").replacement((matchResult, builder) -> { - return Component.text("").append(Component.text("1337")); - }).build()); + final Component replaced = base.replaceText(c -> c.match("value") + .replacement((matchResult, builder) -> Component.text("").append(Component.text("1337")))); final Component expected = Component.text() .append(Component.text("1337")) .build(); assertEquals(expected, replaced); } + + @Test + void testReplaceWithMatchResultCondition() { + final Component base = Component.text("get the set the get the set the get the set"); + + final Component replaced = base.replaceText(c -> c.match("[gs]et") + .condition((result, count, replacements) -> result.group().equals("set") ? PatternReplacementResult.REPLACE : PatternReplacementResult.CONTINUE) + .replacement("pet")); + + final Component expected = Component.text().content("get the ") + .append( + Component.text("pet"), + Component.text(" the get the "), + Component.text("pet"), + Component.text(" the get the "), + Component.text("pet") + ) + .build(); + assertEquals(expected, replaced); + } }