Skip to content

Commit 905450f

Browse files
committed
fix: component flattener not popping styles in correct order
Closes #1254
1 parent 2f48b6b commit 905450f

File tree

2 files changed

+57
-6
lines changed

2 files changed

+57
-6
lines changed

api/src/main/java/net/kyori/adventure/text/flattener/ComponentFlattenerImpl.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,12 @@ final class ComponentFlattenerImpl implements ComponentFlattener {
7979
private static final class StackEntry {
8080
final Component component;
8181
final int depth;
82+
final int stylesToPop;
8283

83-
StackEntry(final Component component, final int depth) {
84+
StackEntry(final Component component, final int depth, final int stylesToPop) {
8485
this.component = component;
8586
this.depth = depth;
87+
this.stylesToPop = stylesToPop;
8688
}
8789
}
8890

@@ -104,7 +106,7 @@ private void flatten0(final @NotNull Component input, final @NotNull FlattenerLi
104106
final Deque<Style> styleStack = new ArrayDeque<>();
105107

106108
// Push the starting component.
107-
componentStack.push(new StackEntry(input, depth));
109+
componentStack.push(new StackEntry(input, depth, 1));
108110

109111
while (!componentStack.isEmpty()) {
110112
final StackEntry entry = componentStack.pop();
@@ -130,12 +132,19 @@ private void flatten0(final @NotNull Component input, final @NotNull FlattenerLi
130132
// Push any children onto the stack in reverse order so they are popped in the right order.
131133
final List<Component> children = component.children();
132134
for (int i = children.size() - 1; i >= 0; i--) {
133-
componentStack.push(new StackEntry(children.get(i), currentDepth + 1));
135+
if (i == children.size() - 1) {
136+
// The last child is responsible for popping all the parents' styles.
137+
componentStack.push(new StackEntry(children.get(i), currentDepth + 1, entry.stylesToPop + 1));
138+
} else {
139+
componentStack.push(new StackEntry(children.get(i), currentDepth + 1, 1));
140+
}
134141
}
135142
} else {
136-
// If there are no children, we pop the latest style to go back "up" the tree.
137-
final Style style = styleStack.pop();
138-
listener.popStyle(style);
143+
// If there are no children, we pop the latest N styles to go back "up" the tree.
144+
for (int i = entry.stylesToPop; i > 0; i--) {
145+
final Style style = styleStack.pop();
146+
listener.popStyle(style);
147+
}
139148
}
140149
}
141150

api/src/test/java/net/kyori/adventure/text/flattener/ComponentFlattenerTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import net.kyori.adventure.text.NBTComponent;
3535
import net.kyori.adventure.text.TranslatableComponent;
3636
import net.kyori.adventure.text.TranslationArgument;
37+
import net.kyori.adventure.text.event.ClickEvent;
3738
import net.kyori.adventure.text.format.NamedTextColor;
3839
import net.kyori.adventure.text.format.Style;
3940
import net.kyori.adventure.text.format.TextDecoration;
@@ -52,6 +53,7 @@ static class TrackingFlattener implements FlattenerListener {
5253
int pushCount;
5354
int popCount;
5455
final List<Style> pushedStyles = new ArrayList<>();
56+
final List<Style> poppedStyles = new ArrayList<>();
5557
final List<String> strings = new ArrayList<>();
5658

5759
@Override
@@ -68,6 +70,7 @@ public void component(final @NotNull String text) {
6870
@Override
6971
public void popStyle(final @NotNull Style style) {
7072
this.popCount++;
73+
this.poppedStyles.add(style);
7174
}
7275

7376
public TrackingFlattener assertBalanced() {
@@ -90,6 +93,11 @@ public TrackingFlattener assertStyles(final Style... styles) {
9093
assertIterableEquals(Arrays.asList(styles), this.pushedStyles);
9194
return this;
9295
}
96+
97+
public TrackingFlattener assertPoppedStyles(final Style... styles) {
98+
assertIterableEquals(Arrays.asList(styles), this.poppedStyles);
99+
return this;
100+
}
93101
}
94102

95103
static class CancellingFlattener extends TrackingFlattener {
@@ -154,6 +162,40 @@ void testComplex() {
154162
.assertContents("Hi there my", " blue ", "friend");
155163
}
156164

165+
@Test
166+
void testComplexNested() {
167+
final Component input = Component.text()
168+
.content("Hi there my")
169+
.append(Component.text(" clickable ")
170+
.clickEvent(ClickEvent.copyToClipboard("some text"))
171+
.append(
172+
Component.text("and bold ")
173+
.decorate(TextDecoration.BOLD)
174+
.append(Component.text("red ", NamedTextColor.RED)))
175+
.append(
176+
Component.text("and blue ", NamedTextColor.BLUE)))
177+
.append(Component.text("friend"))
178+
.build();
179+
180+
this.testFlatten(ComponentFlattener.basic(), input).assertBalanced()
181+
.assertPushesAndPops(6)
182+
.assertStyles(
183+
Style.empty(),
184+
Style.style(ClickEvent.copyToClipboard("some text")),
185+
Style.style(TextDecoration.BOLD),
186+
Style.style(NamedTextColor.RED),
187+
Style.style(NamedTextColor.BLUE),
188+
Style.empty())
189+
.assertPoppedStyles(
190+
Style.style(NamedTextColor.RED),
191+
Style.style(TextDecoration.BOLD),
192+
Style.style(NamedTextColor.BLUE),
193+
Style.style(ClickEvent.copyToClipboard("some text")),
194+
Style.empty(),
195+
Style.empty())
196+
.assertContents("Hi there my", " clickable ", "and bold ", "red ", "and blue ", "friend");
197+
}
198+
157199
@Test
158200
void testTranslatable() {
159201
this.testFlatten(ComponentFlattener.basic(), Component.translatable("adventure.test.key"))

0 commit comments

Comments
 (0)