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 ab7626b38..3ff1ceb27 100644 --- a/api/src/main/java/net/kyori/adventure/text/TextReplacementRenderer.java +++ b/api/src/main/java/net/kyori/adventure/text/TextReplacementRenderer.java @@ -47,6 +47,8 @@ private TextReplacementRenderer() { @Override public @NonNull Component render(final @NonNull Component component, final @NonNull State state) { if(!state.running) return component; + final boolean prevFirstMatch = state.firstMatch; + state.firstMatch = true; final List oldChildren = component.children(); final int oldChildrenSize = oldChildren.size(); @@ -90,7 +92,7 @@ private TextReplacementRenderer() { if(children == null) { children = new ArrayList<>(oldChildrenSize + 2); } - if(state.replaceCount == 0) { + if(state.firstMatch) { // truncate parent to content before match modified = ((TextComponent) component).content(content.substring(0, matcher.start())); } else if(replacedUntil < matcher.start()) { @@ -102,6 +104,7 @@ private TextReplacementRenderer() { } } state.replaceCount++; + state.firstMatch = false; replacedUntil = matcher.end(); } if(replacedUntil < content.length()) { @@ -171,6 +174,7 @@ private TextReplacementRenderer() { } } + state.firstMatch = prevFirstMatch; // Update the modified component with new children if(children != null) { return modified.children(children); @@ -185,6 +189,7 @@ static final class State { 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) { this.pattern = pattern; diff --git a/api/src/test/java/net/kyori/adventure/text/TextComponentTest.java b/api/src/test/java/net/kyori/adventure/text/TextComponentTest.java index 9389b1d86..02a6dbc72 100644 --- a/api/src/test/java/net/kyori/adventure/text/TextComponentTest.java +++ b/api/src/test/java/net/kyori/adventure/text/TextComponentTest.java @@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableSet; import java.util.Collections; +import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.IntStream; import net.kyori.adventure.text.event.HoverEvent; @@ -272,7 +273,35 @@ void testPartialReplaceHasIsolatedStyle() { }); assertEquals(expectedReplacement, component.replaceText(Pattern.compile("Hello"), builder -> builder.content("Goodbye").color(NamedTextColor.LIGHT_PURPLE))); + } + + // https://github.com/KyoriPowered/adventure/issues/197 + @Test + void testReplaceSameInComponentAndEvent() { + final Component original = Component.text("/{0}", NamedTextColor.RED) + .hoverEvent(Component.text() + .append(Component.text("Click to run")) + .append(Component.space()) + .append(Component.text("/{0}", NamedTextColor.RED)).build()); + + final Component expected = Component.text("/", NamedTextColor.RED) + .append(Component.text("mycommand")) + .hoverEvent(Component.text() + .append(Component.text("Click to run")) + .append(Component.space()) + .append(Component.text("/", NamedTextColor.RED).append(Component.text("mycommand"))).build()); + + final Pattern replaceWith = Pattern.compile("\\{(\\d+)}"); + + final Component replaced = original.replaceText(replaceWith, b -> { + final Matcher matcher = replaceWith.matcher(b.content()); + assertTrue(matcher.find()); + assertEquals(0, Integer.parseInt(matcher.group(1))); // only one index in our test case + + return Component.text("mycommand"); + }); + assertEquals(expected, replaced); } @Test