From 0b813e43f7d16cfe62c17db26aaeb84bdb11c89b Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 11 Mar 2020 12:43:20 +0300 Subject: [PATCH] BlockHandler abstraction in core module --- CHANGELOG.md | 1 + .../io/noties/markwon/BlockHandlerDef.java | 23 ++++++ .../io/noties/markwon/MarkwonVisitor.java | 33 +++++++++ .../io/noties/markwon/MarkwonVisitorImpl.java | 34 ++++++++- .../markwon/SoftBreakAddsNewLinePlugin.java | 2 +- .../io/noties/markwon/core/CorePlugin.java | 35 +++------- .../markwon/core/SimpleBlockNodeVisitor.java | 9 +-- .../markwon/AbstractMarkwonVisitorImpl.java | 5 +- .../markwon/MarkwonVisitorImplTest.java | 24 ++++--- .../noties/markwon/core/CorePluginTest.java | 6 ++ .../markwon/syntax/SyntaxHighlightTest.java | 3 +- .../markwon/ext/latex/JLatexMathPlugin.java | 7 +- .../markwon/ext/tables/TablePlugin.java | 11 +-- .../basicplugins/BasicPluginsActivity.java | 70 ++++++++++++++++++- 14 files changed, 207 insertions(+), 56 deletions(-) create mode 100644 markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java diff --git a/CHANGELOG.md b/CHANGELOG.md index eb32a054..c2b1767d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ dependency (must be explicitly added to `Markwon` whilst configuring) * non-empty bounds for `AsyncDrawable` when no dimensions are not yet available ([#189]) * `linkify` - option to use `LinkifyCompat` in `LinkifyPlugin` ([#201])
Thanks to [@drakeet] +* `MarkwonVisitor.BlockHandler` and `BlockHandlerDef` implementation to control how blocks insert new lines after them ```java diff --git a/markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java b/markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java new file mode 100644 index 00000000..0c5b3b49 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/BlockHandlerDef.java @@ -0,0 +1,23 @@ +package io.noties.markwon; + +import androidx.annotation.NonNull; + +import org.commonmark.node.Node; + +/** + * @since $nap; + */ +public class BlockHandlerDef implements MarkwonVisitor.BlockHandler { + @Override + public void blockStart(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + visitor.ensureNewLine(); + } + + @Override + public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + if (visitor.hasNext(node)) { + visitor.ensureNewLine(); + visitor.forceNewLine(); + } + } +} diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java index 9fd6ffc7..f9c4554c 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitor.java @@ -23,6 +23,19 @@ public interface MarkwonVisitor extends Visitor { void visit(@NonNull MarkwonVisitor visitor, @NonNull N n); } + /** + * Primary purpose is to control the spacing applied before/after certain blocks, which + * visitors are created elsewhere + * + * @since $nap; + */ + interface BlockHandler { + + void blockStart(@NonNull MarkwonVisitor visitor, @NonNull Node node); + + void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node); + } + interface Builder { /** @@ -33,6 +46,16 @@ public interface MarkwonVisitor extends Visitor { @NonNull Builder on(@NonNull Class node, @Nullable NodeVisitor nodeVisitor); + /** + * @param blockHandler to handle block start/end + * @see BlockHandler + * @see BlockHandlerDef + * @since $nap; + */ + @SuppressWarnings("UnusedReturnValue") + @NonNull + Builder blockHandler(@NonNull BlockHandler blockHandler); + @NonNull MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps); } @@ -133,4 +156,14 @@ public interface MarkwonVisitor extends Visitor { */ @SuppressWarnings("unused") void setSpansForNodeOptional(@NonNull Class node, int start); + + /** + * @since $nap; + */ + void blockStart(@NonNull Node node); + + /** + * @since $nap; + */ + void blockEnd(@NonNull Node node); } diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java index 659a6622..bd32352e 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java @@ -45,15 +45,20 @@ class MarkwonVisitorImpl implements MarkwonVisitor { private final Map, NodeVisitor> nodes; + // @since $nap; + private final BlockHandler blockHandler; + MarkwonVisitorImpl( @NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull SpannableBuilder builder, - @NonNull Map, NodeVisitor> nodes) { + @NonNull Map, NodeVisitor> nodes, + @NonNull BlockHandler blockHandler) { this.configuration = configuration; this.renderProps = renderProps; this.builder = builder; this.nodes = nodes; + this.blockHandler = blockHandler; } @Override @@ -268,9 +273,20 @@ class MarkwonVisitorImpl implements MarkwonVisitor { } } + @Override + public void blockStart(@NonNull Node node) { + blockHandler.blockStart(this, node); + } + + @Override + public void blockEnd(@NonNull Node node) { + blockHandler.blockEnd(this, node); + } + static class BuilderImpl implements Builder { private final Map, NodeVisitor> nodes = new HashMap<>(); + private BlockHandler blockHandler; @NonNull @Override @@ -290,14 +306,28 @@ class MarkwonVisitorImpl implements MarkwonVisitor { return this; } + @NonNull + @Override + public Builder blockHandler(@NonNull BlockHandler blockHandler) { + this.blockHandler = blockHandler; + return this; + } + @NonNull @Override public MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps) { + // @since $nap; + BlockHandler blockHandler = this.blockHandler; + if (blockHandler == null) { + blockHandler = new BlockHandlerDef(); + } + return new MarkwonVisitorImpl( configuration, renderProps, new SpannableBuilder(), - Collections.unmodifiableMap(nodes)); + Collections.unmodifiableMap(nodes), + blockHandler); } } } diff --git a/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java b/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java index 6f49ac68..f1aff45d 100644 --- a/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/SoftBreakAddsNewLinePlugin.java @@ -19,7 +19,7 @@ public class SoftBreakAddsNewLinePlugin extends AbstractMarkwonPlugin { builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor() { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) { - visitor.forceNewLine(); + visitor.ensureNewLine(); } }); } diff --git a/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java b/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java index dffa215d..29c63a2a 100644 --- a/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java @@ -210,17 +210,14 @@ public class CorePlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull BlockQuote blockQuote) { - visitor.ensureNewLine(); + visitor.blockStart(blockQuote); final int length = visitor.length(); visitor.visitChildren(blockQuote); visitor.setSpansForNodeOptional(blockQuote, length); - if (visitor.hasNext(blockQuote)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(blockQuote); } }); } @@ -316,7 +313,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { @NonNull String code, @NonNull Node node) { - visitor.ensureNewLine(); + visitor.blockStart(node); final int length = visitor.length(); @@ -333,10 +330,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { visitor.setSpansForNodeOptional(node, length); - if (visitor.hasNext(node)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(node); } private static void bulletList(@NonNull MarkwonVisitor.Builder builder) { @@ -402,7 +396,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull ThematicBreak thematicBreak) { - visitor.ensureNewLine(); + visitor.blockStart(thematicBreak); final int length = visitor.length(); @@ -411,10 +405,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { visitor.setSpansForNodeOptional(thematicBreak, length); - if (visitor.hasNext(thematicBreak)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(thematicBreak); } }); } @@ -424,7 +415,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull Heading heading) { - visitor.ensureNewLine(); + visitor.blockStart(heading); final int length = visitor.length(); visitor.visitChildren(heading); @@ -433,10 +424,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { visitor.setSpansForNodeOptional(heading, length); - if (visitor.hasNext(heading)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(heading); } }); } @@ -467,7 +455,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { final boolean inTightList = isInTightList(paragraph); if (!inTightList) { - visitor.ensureNewLine(); + visitor.blockStart(paragraph); } final int length = visitor.length(); @@ -478,9 +466,8 @@ public class CorePlugin extends AbstractMarkwonPlugin { // @since 1.1.1 apply paragraph span visitor.setSpansForNodeOptional(paragraph, length); - if (!inTightList && visitor.hasNext(paragraph)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); + if (!inTightList) { + visitor.blockEnd(paragraph); } } }); diff --git a/markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java b/markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java index 91742773..6a27599e 100644 --- a/markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/SimpleBlockNodeVisitor.java @@ -17,19 +17,16 @@ public class SimpleBlockNodeVisitor implements MarkwonVisitor.NodeVisitor @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + visitor.blockStart(node); + // @since 3.0.1 we keep track of start in order to apply spans (optionally) final int length = visitor.length(); - visitor.ensureNewLine(); - visitor.visitChildren(node); // @since 3.0.1 we apply optional spans visitor.setSpansForNodeOptional(node, length); - if (visitor.hasNext(node)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(node); } } diff --git a/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java b/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java index ed4dae6b..5bae81f9 100644 --- a/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java +++ b/markwon-core/src/test/java/io/noties/markwon/AbstractMarkwonVisitorImpl.java @@ -12,7 +12,8 @@ public class AbstractMarkwonVisitorImpl extends MarkwonVisitorImpl { @NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull SpannableBuilder spannableBuilder, - @NonNull Map, NodeVisitor> nodes) { - super(configuration, renderProps, spannableBuilder, nodes); + @NonNull Map, NodeVisitor> nodes, + @NonNull BlockHandler blockHandler) { + super(configuration, renderProps, spannableBuilder, nodes, blockHandler); } } diff --git a/markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java index 45387afb..6c6c93ba 100644 --- a/markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonVisitorImplTest.java @@ -43,7 +43,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), renderProps, spannableBuilder, - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); impl.clear(); @@ -61,7 +62,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), mock(RenderProps.class), builder, - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); // at the start - won't add anything impl.ensureNewLine(); @@ -92,7 +94,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), mock(RenderProps.class), builder, - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); assertEquals(0, builder.length()); @@ -144,7 +147,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), mock(RenderProps.class), mock(SpannableBuilder.class), - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); final BlockQuote node = mock(BlockQuote.class); final Node child = mock(Node.class); @@ -163,7 +167,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), mock(RenderProps.class), mock(SpannableBuilder.class), - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); final Node noNext = mock(Node.class); assertFalse(impl.hasNext(noNext)); @@ -195,7 +200,8 @@ public class MarkwonVisitorImplTest { mock(MarkwonConfiguration.class), mock(RenderProps.class), builder, - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); for (int i = 0; i < 13; i++) { builder.setLength(i); @@ -221,7 +227,8 @@ public class MarkwonVisitorImplTest { configuration, mock(RenderProps.class), mock(SpannableBuilder.class), - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); impl.setSpansForNode(Node.class, 0); @@ -252,7 +259,8 @@ public class MarkwonVisitorImplTest { configuration, mock(RenderProps.class), builder, - Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + Collections., MarkwonVisitor.NodeVisitor>emptyMap(), + mock(MarkwonVisitor.BlockHandler.class)); // append something builder.append("no-spans-test"); diff --git a/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java index 9cb95028..56bc539c 100644 --- a/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java @@ -107,6 +107,12 @@ public class CorePluginTest { return this; } + @NonNull + @Override + public MarkwonVisitor.Builder blockHandler(@NonNull MarkwonVisitor.BlockHandler blockHandler) { + throw new RuntimeException(); + } + @NonNull @Override public MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps) { diff --git a/markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java b/markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java index fdde1888..3ff6df03 100644 --- a/markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/syntax/SyntaxHighlightTest.java @@ -91,7 +91,8 @@ public class SyntaxHighlightTest { configuration, mock(RenderProps.class), new SpannableBuilder(), - visitorMap); + visitorMap, + mock(MarkwonVisitor.BlockHandler.class)); final SpannableBuilder builder = visitor.builder(); diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index d0886a14..f39b53af 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -191,7 +191,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathBlock jLatexMathBlock) { - visitor.ensureNewLine(); + visitor.blockStart(jLatexMathBlock); final String latex = jLatexMathBlock.latex(); @@ -217,10 +217,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin { visitor.setSpans(length, span); - if (visitor.hasNext(jLatexMathBlock)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } + visitor.blockEnd(jLatexMathBlock); } }); } diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java index 086afd75..0cbce8b8 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java @@ -121,12 +121,15 @@ public class TablePlugin extends AbstractMarkwonPlugin { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableBlock tableBlock) { + visitor.blockStart(tableBlock); + visitor.visitChildren(tableBlock); - if (visitor.hasNext(tableBlock)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } +// if (visitor.hasNext(tableBlock)) { +// visitor.ensureNewLine(); +// visitor.forceNewLine(); +// } + visitor.blockEnd(tableBlock); } }) .on(TableBody.class, new MarkwonVisitor.NodeVisitor() { diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index 02878480..632c94ec 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -11,19 +11,19 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.commonmark.node.Heading; +import org.commonmark.node.Node; import org.commonmark.node.Paragraph; import java.util.Collection; import java.util.Collections; import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.BlockHandlerDef; import io.noties.markwon.Markwon; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonSpansFactory; import io.noties.markwon.MarkwonVisitor; -import io.noties.markwon.RenderProps; import io.noties.markwon.SoftBreakAddsNewLinePlugin; -import io.noties.markwon.SpanFactory; import io.noties.markwon.core.CoreProps; import io.noties.markwon.core.MarkwonTheme; import io.noties.markwon.core.spans.LastLineSpacingSpan; @@ -52,7 +52,9 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { .add("softBreakAddsSpace", this::softBreakAddsSpace) .add("softBreakAddsNewLine", this::softBreakAddsNewLine) .add("additionalSpacing", this::additionalSpacing) - .add("headingNoSpace", this::headingNoSpace); + .add("headingNoSpace", this::headingNoSpace) + .add("headingNoSpaceBlockHandler", this::headingNoSpaceBlockHandler) + .add("allBlocksNoForcedLine", this::allBlocksNoForcedLine); } @Override @@ -311,6 +313,68 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { markwon.setMarkdown(textView, md); } + private void headingNoSpaceBlockHandler() { + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.blockHandler(new BlockHandlerDef() { + @Override + public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + if (node instanceof Heading) { + if (visitor.hasNext(node)) { + visitor.ensureNewLine(); + // ensure new line but do not force insert one + } + } else { + super.blockEnd(visitor, node); + } + } + }); + } + }) + .build(); + + final String md = "" + + "# Title title title title title title title title title title \n\ntext text text text"; + + markwon.setMarkdown(textView, md); + } + + private void allBlocksNoForcedLine() { + final MarkwonVisitor.BlockHandler blockHandler = new BlockHandlerDef() { + @Override + public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + if (visitor.hasNext(node)) { + visitor.ensureNewLine(); + } + } + }; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.blockHandler(blockHandler); + } + }) + .build(); + + final String md = "" + + "# Hello there!\n\n" + + "* a first\n" + + "* second\n" + + "- third\n" + + "* * nested one\n\n" + + "> block quote\n\n" + + "> > and nested one\n\n" + + "```java\n" + + "final int i = 0;\n" + + "```\n\n"; + + markwon.setMarkdown(textView, md); + } + // public void step_6() { // // final Markwon markwon = Markwon.builder(this)