Repository: kyoripowered/adventure
Issue: Loss of style when replacing full match text

When replacing text with a complete match of the text of a component with another component (`ComponentLike`) without a style (primitive text without any style), the outer style is lost, although it should be inherited. If you do a partial match, such as adding spaces at the end or beginning of the original component, the style is inherited correctly.

I found the code that is responsible for this. However, I don’t know how to fix it correctly.
https://github.com/KyoriPowered/adventure/blob/e23407ac6c0d4122cc4646d2f9e9fa1328a0423a/api/src/main/java/net/kyori/adventure/text/TextReplacementConfig.java#L193-L197
https://github.com/KyoriPowered/adventure/blob/e23407ac6c0d4122cc4646d2f9e9fa1328a0423a/api/src/main/java/net/kyori/adventure/text/TextReplacementRenderer.java#L75-L76

Changed files:
api/src/main/java/net/kyori/adventure/text/TextReplacementRenderer.java, api/src/test/java/net/kyori/adventure/text/TextReplacementRendererTest.java

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 4b2f28b9..483330ce 100644
--- a/api/src/main/java/net/kyori/adventure/text/TextReplacementRenderer.java
+++ b/api/src/main/java/net/kyori/adventure/text/TextReplacementRenderer.java
@@ -30,6 +30,7 @@ import java.util.regex.MatchResult;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import net.kyori.adventure.text.event.HoverEvent;
+import net.kyori.adventure.text.format.Style;
 import net.kyori.adventure.text.renderer.ComponentRenderer;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -76,6 +77,10 @@ final class TextReplacementRenderer implements ComponentRenderer<TextReplacement
               .style(component.style()));
 
             modified = replacement == null ? Component.empty() : replacement.asComponent();
+
+            // merge style of the match into this component to prevent unexpected loss of style
+            modified = modified.style(modified.style().merge(component.style(), Style.Merge.Strategy.IF_ABSENT_ON_TARGET));
+
             if (children == null) { // Prepare children
               children = new ArrayList<>(oldChildrenSize + modified.children().size());
               children.addAll(modified.children());
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 c2c546e7..f39cc94e 100644
--- a/api/src/test/java/net/kyori/adventure/text/TextReplacementRendererTest.java
+++ b/api/src/test/java/net/kyori/adventure/text/TextReplacementRendererTest.java
@@ -246,4 +246,16 @@ class TextReplacementRendererTest {
       .build();
     assertEquals(expected, replaced);
   }
+
+  // https://github.com/KyoriPowered/adventure/issues/387
+  @Test
+  void testFullMatchReplaceWithStyle() {
+    final Component base = Component.text("hello", NamedTextColor.RED);
+
+    final Component replaced = base.replaceText(c -> c.matchLiteral("hello")
+      .replacement(Component.text("world").decorate(TextDecoration.BOLD)));
+
+    final Component expected = Component.text("world", NamedTextColor.RED, TextDecoration.BOLD);
+    assertEquals(expected, replaced);
+  }
 }