From d42ae41409c82c01c7d7e009a06e39c9cfd427bc Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 12 May 2020 14:41:24 +0300 Subject: [PATCH] Add fallbackToRawInputWhenEmpty configuration --- CHANGELOG.md | 4 +- .../main/java/io/noties/markwon/Markwon.java | 11 +++ .../io/noties/markwon/MarkwonBuilderImpl.java | 13 ++- .../java/io/noties/markwon/MarkwonImpl.java | 23 +++++- .../io/noties/markwon/MarkwonImplTest.java | 79 ++++++++++++++++--- 5 files changed, 116 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 293be7ea..a4607ed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,13 @@ * Expose `enabledBlockTypes` in `CorePlugin` * Update `jlatexmath-android` dependency ([#225]) * Update `image-coil` module (Coil version `0.10.1`) ([#244])
Thanks to [@tylerbwong] -* Rename `UrlProcessor` to `ImageDestinationProcessor` (`io.noties.markwon.urlprocessor` -> `io.noties.markwon.image.destination`) and limit its usage to process **only** destination URL of images (was used to also process links before) +* Rename `UrlProcessor` to `ImageDestinationProcessor` (`io.noties.markwon.urlprocessor` -> `io.noties.markwon.image.destination`) and limit its usage to process **only** destination URL of images (was used to also process links before) +* `fallbackToRawInputWhenEmpty` `Markwon.Builder` configuration to fallback to raw input if rendered markdown is empty ([#242]) [#235]: https://github.com/noties/Markwon/issues/235 [#225]: https://github.com/noties/Markwon/issues/225 [#244]: https://github.com/noties/Markwon/pull/244 +[#242]: https://github.com/noties/Markwon/issues/242 [@tylerbwong]: https://github.com/tylerbwong diff --git a/markwon-core/src/main/java/io/noties/markwon/Markwon.java b/markwon-core/src/main/java/io/noties/markwon/Markwon.java index cd277e03..1422bf3c 100644 --- a/markwon-core/src/main/java/io/noties/markwon/Markwon.java +++ b/markwon-core/src/main/java/io/noties/markwon/Markwon.java @@ -192,6 +192,17 @@ public abstract class Markwon { @NonNull Builder usePlugins(@NonNull Iterable plugins); + /** + * Control if small chunks of non-finished markdown sentences (for example, a single `*` character) + * should be displayed/rendered as raw input instead of an empty string. + *

+ * Since $nap; {@code true} by default, versions prior - {@code false} + * + * @since $nap; + */ + @NonNull + Builder fallbackToRawInputWhenEmpty(boolean fallbackToRawInputWhenEmpty); + @NonNull Markwon build(); } diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java index 2ae70e18..9d470f87 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java @@ -27,6 +27,9 @@ class MarkwonBuilderImpl implements Markwon.Builder { private Markwon.TextSetter textSetter; + // @since $nap; + private boolean fallbackToRawInputWhenEmpty = true; + MarkwonBuilderImpl(@NonNull Context context) { this.context = context; } @@ -71,6 +74,13 @@ class MarkwonBuilderImpl implements Markwon.Builder { return this; } + @NonNull + @Override + public Markwon.Builder fallbackToRawInputWhenEmpty(boolean fallbackToRawInputWhenEmpty) { + this.fallbackToRawInputWhenEmpty = fallbackToRawInputWhenEmpty; + return this; + } + @NonNull @Override public Markwon build() { @@ -114,7 +124,8 @@ class MarkwonBuilderImpl implements Markwon.Builder { parserBuilder.build(), visitorFactory, configuration, - Collections.unmodifiableList(plugins) + Collections.unmodifiableList(plugins), + fallbackToRawInputWhenEmpty ); } diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java index 5aced55c..999e2f68 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java @@ -1,6 +1,8 @@ package io.noties.markwon; +import android.text.SpannableStringBuilder; import android.text.Spanned; +import android.text.TextUtils; import android.widget.TextView; import androidx.annotation.NonNull; @@ -28,19 +30,25 @@ class MarkwonImpl extends Markwon { @Nullable private final TextSetter textSetter; + // @since $nap; + private final boolean fallbackToRawInputWhenEmpty; + MarkwonImpl( @NonNull TextView.BufferType bufferType, @Nullable TextSetter textSetter, @NonNull Parser parser, @NonNull MarkwonVisitorFactory visitorFactory, @NonNull MarkwonConfiguration configuration, - @NonNull List plugins) { + @NonNull List plugins, + boolean fallbackToRawInputWhenEmpty + ) { this.bufferType = bufferType; this.textSetter = textSetter; this.parser = parser; this.visitorFactory = visitorFactory; this.configuration = configuration; this.plugins = plugins; + this.fallbackToRawInputWhenEmpty = fallbackToRawInputWhenEmpty; } @NonNull @@ -86,7 +94,18 @@ class MarkwonImpl extends Markwon { @NonNull @Override public Spanned toMarkdown(@NonNull String input) { - return render(parse(input)); + final Spanned spanned = render(parse(input)); + + // @since $nap; + // if spanned is empty, we are configured to use raw input and input is not empty + if (TextUtils.isEmpty(spanned) + && fallbackToRawInputWhenEmpty + && !TextUtils.isEmpty(input)) { + // let's use SpannableStringBuilder in order to keep backward-compatibility + return new SpannableStringBuilder(input); + } + + return spanned; } @Override diff --git a/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java index 0d9024d4..599e24d5 100644 --- a/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java @@ -1,6 +1,7 @@ package io.noties.markwon; import android.text.Spanned; +import android.text.TextUtils; import android.widget.TextView; import org.commonmark.node.Node; @@ -50,7 +51,9 @@ public class MarkwonImplTest { mock(Parser.class), mock(MarkwonVisitorFactory.class), mock(MarkwonConfiguration.class), - Collections.singletonList(plugin)); + Collections.singletonList(plugin), + true + ); impl.parse("whatever"); @@ -74,7 +77,9 @@ public class MarkwonImplTest { parser, mock(MarkwonVisitorFactory.class), mock(MarkwonConfiguration.class), - Arrays.asList(first, second)); + Arrays.asList(first, second), + true + ); impl.parse("zero"); @@ -102,7 +107,9 @@ public class MarkwonImplTest { mock(Parser.class), visitorFactory, mock(MarkwonConfiguration.class), - Collections.singletonList(plugin)); + Collections.singletonList(plugin), + true + ); when(visitorFactory.create()).thenReturn(visitor); when(visitor.builder()).thenReturn(builder); @@ -149,7 +156,9 @@ public class MarkwonImplTest { mock(Parser.class), visitorFactory, mock(MarkwonConfiguration.class), - Collections.emptyList()); + Collections.emptyList(), + true + ); impl.render(mock(Node.class)); @@ -185,7 +194,9 @@ public class MarkwonImplTest { mock(Parser.class), visitorFactory, mock(MarkwonConfiguration.class), - Collections.singletonList(plugin)); + Collections.singletonList(plugin), + true + ); final AtomicBoolean flag = new AtomicBoolean(false); final Node node = mock(Node.class); @@ -224,7 +235,9 @@ public class MarkwonImplTest { mock(Parser.class), mock(MarkwonVisitorFactory.class, RETURNS_MOCKS), mock(MarkwonConfiguration.class), - Collections.singletonList(plugin)); + Collections.singletonList(plugin), + true + ); final TextView textView = mock(TextView.class); final AtomicBoolean flag = new AtomicBoolean(false); @@ -272,7 +285,9 @@ public class MarkwonImplTest { mock(Parser.class), mock(MarkwonVisitorFactory.class), mock(MarkwonConfiguration.class), - plugins); + plugins, + true + ); assertTrue("First", impl.hasPlugin(First.class)); assertFalse("Second", impl.hasPlugin(Second.class)); @@ -295,7 +310,9 @@ public class MarkwonImplTest { mock(Parser.class), mock(MarkwonVisitorFactory.class), mock(MarkwonConfiguration.class), - Collections.singletonList(plugin)); + Collections.singletonList(plugin), + true + ); final TextView textView = mock(TextView.class); final Spanned spanned = mock(Spanned.class); @@ -339,7 +356,9 @@ public class MarkwonImplTest { mock(Parser.class), mock(MarkwonVisitorFactory.class), mock(MarkwonConfiguration.class), - plugins); + plugins, + true + ); // should be returned assertNotNull(impl.requirePlugin(MarkwonPlugin.class)); @@ -370,7 +389,9 @@ public class MarkwonImplTest { mock(Parser.class), mock(MarkwonVisitorFactory.class), mock(MarkwonConfiguration.class), - plugins); + plugins, + true + ); final List list = impl.getPlugins(); @@ -385,4 +406,42 @@ public class MarkwonImplTest { assertTrue(e.getMessage(), true); } } + + @Test + public void fallback_to_raw() { + final String md = "*"; + + final MarkwonImpl impl = new MarkwonImpl( + TextView.BufferType.SPANNABLE, + null, + mock(Parser.class, RETURNS_MOCKS), + // it must be sufficient to just return mocks and thus empty rendering result + mock(MarkwonVisitorFactory.class, RETURNS_MOCKS), + mock(MarkwonConfiguration.class), + Collections.emptyList(), + true + ); + + final Spanned spanned = impl.toMarkdown(md); + assertEquals(md, spanned.toString()); + } + + @Test + public void fallback_to_raw_false() { + final String md = "*"; + + final MarkwonImpl impl = new MarkwonImpl( + TextView.BufferType.SPANNABLE, + null, + mock(Parser.class, RETURNS_MOCKS), + // it must be sufficient to just return mocks and thus empty rendering result + mock(MarkwonVisitorFactory.class, RETURNS_MOCKS), + mock(MarkwonConfiguration.class), + Collections.emptyList(), + false + ); + + final Spanned spanned = impl.toMarkdown(md); + assertTrue(spanned.toString(), TextUtils.isEmpty(spanned)); + } } \ No newline at end of file