diff --git a/api/src/main/java/net/kyori/adventure/text/flattener/ComponentFlattenerImpl.java b/api/src/main/java/net/kyori/adventure/text/flattener/ComponentFlattenerImpl.java index 51c1987b7..ddc3cc6a3 100644 --- a/api/src/main/java/net/kyori/adventure/text/flattener/ComponentFlattenerImpl.java +++ b/api/src/main/java/net/kyori/adventure/text/flattener/ComponentFlattenerImpl.java @@ -99,7 +99,7 @@ private void flatten0(final @NotNull Component input, final @NotNull FlattenerLi flattener.handle(input, listener, depth + 1); } - if (!input.children().isEmpty()) { + if (!input.children().isEmpty() && listener.shouldContinue()) { for (final Component child : input.children()) { this.flatten0(child, listener, depth + 1); } diff --git a/api/src/main/java/net/kyori/adventure/text/flattener/FlattenerListener.java b/api/src/main/java/net/kyori/adventure/text/flattener/FlattenerListener.java index 91584f6d5..83f9b9b0c 100644 --- a/api/src/main/java/net/kyori/adventure/text/flattener/FlattenerListener.java +++ b/api/src/main/java/net/kyori/adventure/text/flattener/FlattenerListener.java @@ -50,6 +50,16 @@ default void pushStyle(final @NotNull Style style) { */ void component(final @NotNull String text); + /** + * Determine if the flattener should continue running. + * + * @return {@code true} if the flattener should continue or {@code false} if it should stop + * @since 4.15.0 + */ + default boolean shouldContinue() { + return true; + } + /** * Pop a pushed style. * diff --git a/api/src/test/java/net/kyori/adventure/text/flattener/ComponentFlattenerTest.java b/api/src/test/java/net/kyori/adventure/text/flattener/ComponentFlattenerTest.java index 79300b918..a80e7929c 100644 --- a/api/src/test/java/net/kyori/adventure/text/flattener/ComponentFlattenerTest.java +++ b/api/src/test/java/net/kyori/adventure/text/flattener/ComponentFlattenerTest.java @@ -88,12 +88,31 @@ public TrackingFlattener assertStyles(final Style... styles) { } } + static class CancellingFlattener extends TrackingFlattener { + int maxCount; + + CancellingFlattener(final int maxCount) { + this.maxCount = maxCount; + } + + @Override + public boolean shouldContinue() { + return this.strings.size() < this.maxCount; + } + } + private TrackingFlattener testFlatten(final ComponentFlattener flattener, final Component toFlatten) { final TrackingFlattener listener = new TrackingFlattener(); flattener.flatten(toFlatten, listener); return listener; } + private CancellingFlattener testCancellingFlatten(final ComponentFlattener flattener, final Component toFlatten, final int maxCount) { + final CancellingFlattener listener = new CancellingFlattener(maxCount); + flattener.flatten(toFlatten, listener); + return listener; + } + @Test void testEmpty() { ComponentFlattener.basic().flatten(Component.empty(), input -> Assertions.fail("Should not be called")); @@ -249,4 +268,17 @@ void testFailsWhenInSameHierarchy() { // complex supertype assertThrows(IllegalArgumentException.class, () -> builder.complexMapper(Component.class, ($, $$) -> {})); } + + @Test + void testEarlyExit() { + final Component component = Component.text("Hello") + .append(Component.text("How are you?") + .append(Component.text("Not great") + .append(Component.text("Goodbye")))); + + this.testCancellingFlatten(ComponentFlattener.basic(), component, 3) + .assertBalanced() + .assertPushesAndPops(3) + .assertContents("Hello", "How are you?", "Not great"); + } }