From 2a43797023e3769f1e7f4058cab16e52e79c0ea1 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 1 Aug 2019 12:27:38 +0300 Subject: [PATCH] TablePlugin defer table-row invalidation --- CHANGELOG.md | 3 + .../io/noties/markwon/app/MainActivity.java | 5 ++ app/src/main/res/layout/activity_main.xml | 5 +- .../main/java/io/noties/markwon/Markwon.java | 15 +++++ .../java/io/noties/markwon/MarkwonImpl.java | 19 ++++++ .../io/noties/markwon/MarkwonImplTest.java | 62 +++++++++++++++++++ .../ext/tables/TableRowsScheduler.java | 15 ++++- 7 files changed, 122 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cc0a9ce..ab7a9ce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ * Add `Markwon.TextSetter` interface to be able to use PrecomputedText/PrecomputedTextCompat * Add `PrecomputedTextSetterCompat` and `compileOnly` dependency on `androidx.core:core` (clients must have this dependency in the classpath) +* Add `requirePlugin(Class)` and `getPlugins` for `Markwon` instance +* TablePlugin -> defer table invalidation (via `View.post`), so only one invalidation +happens with each draw-call # 4.0.2 * Fix `JLatexMathPlugin` formula placeholder (cannot have line breaks) ([#149]) diff --git a/app/src/main/java/io/noties/markwon/app/MainActivity.java b/app/src/main/java/io/noties/markwon/app/MainActivity.java index c72542b3..c0cb1390 100644 --- a/app/src/main/java/io/noties/markwon/app/MainActivity.java +++ b/app/src/main/java/io/noties/markwon/app/MainActivity.java @@ -5,6 +5,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.text.Spanned; +import android.text.method.LinkMovementMethod; import android.view.View; import android.widget.TextView; @@ -15,6 +16,7 @@ import javax.inject.Inject; import io.noties.debug.Debug; import io.noties.markwon.Markwon; +import io.noties.markwon.utils.NoCopySpannableFactory; public class MainActivity extends Activity { @@ -60,6 +62,9 @@ public class MainActivity extends Activity { appBarRenderer.render(appBarState()); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setSpannableFactory(NoCopySpannableFactory.getInstance()); + markdownLoader.load(uri(), new MarkdownLoader.OnMarkdownTextLoaded() { @Override public void apply(final String text) { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index da49a67b..46a28b85 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -18,9 +18,12 @@ android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" + android:breakStrategy="simple" + android:hyphenationFrequency="none" android:lineSpacingExtra="2dip" android:textSize="16sp" - tools:text="yo\nman" /> + tools:text="yo\nman" + tools:ignore="UnusedAttribute" /> 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 b02767c2..fa54a3b3 100644 --- a/markwon-core/src/main/java/io/noties/markwon/Markwon.java +++ b/markwon-core/src/main/java/io/noties/markwon/Markwon.java @@ -9,6 +9,8 @@ import androidx.annotation.Nullable; import org.commonmark.node.Node; +import java.util.List; + import io.noties.markwon.core.CorePlugin; /** @@ -119,6 +121,19 @@ public abstract class Markwon { @Nullable public abstract

P getPlugin(@NonNull Class

type); + /** + * @since 4.1.0-SNAPSHOT + */ + @NonNull + public abstract

P requirePlugin(@NonNull Class

type); + + /** + * @return a list of registered {@link MarkwonPlugin} + * @since 4.1.0-SNAPSHOT + */ + @NonNull + public abstract List getPlugins(); + /** * Interface to set text on a TextView. Primary goal is to give a way to use PrecomputedText * functionality 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 3a8d6d1f..3d9c1dcd 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java @@ -9,7 +9,9 @@ import androidx.annotation.Nullable; import org.commonmark.node.Node; import org.commonmark.parser.Parser; +import java.util.Collections; import java.util.List; +import java.util.Locale; /** * @since 3.0.0 @@ -129,4 +131,21 @@ class MarkwonImpl extends Markwon { //noinspection unchecked return (P) out; } + + @NonNull + @Override + public

P requirePlugin(@NonNull Class

type) { + final P plugin = getPlugin(type); + if (plugin == null) { + throw new IllegalStateException(String.format(Locale.US, "Requested plugin `%s` is not " + + "registered with this Markwon instance", type.getName())); + } + return plugin; + } + + @NonNull + @Override + public List getPlugins() { + return Collections.unmodifiableList(plugins); + } } 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 739470b7..8e24e578 100644 --- a/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java @@ -14,6 +14,7 @@ import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -23,6 +24,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -300,4 +302,64 @@ public class MarkwonImplTest { assertEquals(TextView.BufferType.EDITABLE, bufferTypeArgumentCaptor.getValue()); assertNotNull(runnableArgumentCaptor.getValue()); } + + @Test + public void require_plugin_throws() { + // if plugin is `required`, but it's not added -> an exception is thrown + + final class NotPresent extends AbstractMarkwonPlugin { + } + + final List plugins = + Arrays.asList(mock(MarkwonPlugin.class), mock(MarkwonPlugin.class)); + + final MarkwonImpl impl = new MarkwonImpl( + TextView.BufferType.SPANNABLE, + null, + mock(Parser.class), + mock(MarkwonVisitor.class), plugins); + + // should be returned + assertNotNull(impl.requirePlugin(MarkwonPlugin.class)); + + try { + impl.requirePlugin(NotPresent.class); + fail(); + } catch (Throwable t) { + assertTrue(t.getMessage(), t.getMessage().contains(NotPresent.class.getName())); + } + } + + @Test + public void plugins_unmodifiable() { + // returned plugins list must not be modifiable + + // modifiable list (created from Arrays.asList -> which returns non) + final List plugins = new ArrayList<>( + Arrays.asList(mock(MarkwonPlugin.class), mock(MarkwonPlugin.class))); + + // validate that list is modifiable + plugins.add(mock(MarkwonPlugin.class)); + assertEquals(3, plugins.size()); + + final MarkwonImpl impl = new MarkwonImpl( + TextView.BufferType.SPANNABLE, + null, + mock(Parser.class), + mock(MarkwonVisitor.class), + plugins); + + final List list = impl.getPlugins(); + + // instance check (different list) + //noinspection SimplifiableJUnitAssertion + assertTrue(plugins != list); + + try { + list.add(null); + fail(); + } catch (UnsupportedOperationException e) { + assertTrue(e.getMessage(), true); + } + } } \ No newline at end of file diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowsScheduler.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowsScheduler.java index e3086c20..d2be1e29 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowsScheduler.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowsScheduler.java @@ -34,9 +34,22 @@ abstract class TableRowsScheduler { } final TableRowSpan.Invalidator invalidator = new TableRowSpan.Invalidator() { + + // @since 4.1.0-SNAPSHOT + // let's stack-up invalidation calls (so invalidation happens, + // but not with each table-row-span draw call) + final Runnable runnable = new Runnable() { + @Override + public void run() { + view.setText(view.getText()); + } + }; + @Override public void invalidate() { - view.setText(view.getText()); + // @since 4.1.0-SNAPSHOT post invalidation (combine multiple calls) + view.removeCallbacks(runnable); + view.post(runnable); } };