diff --git a/markwon-simple-ext/build.gradle b/markwon-simple-ext/build.gradle
new file mode 100644
index 00000000..39dc63da
--- /dev/null
+++ b/markwon-simple-ext/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'com.android.library'
+
+android {
+
+ compileSdkVersion config['compile-sdk']
+ buildToolsVersion config['build-tools']
+
+ defaultConfig {
+ minSdkVersion config['min-sdk']
+ targetSdkVersion config['target-sdk']
+ versionCode 1
+ versionName version
+ }
+}
+
+dependencies {
+
+ api project(':markwon-core')
+
+ deps['test'].with {
+ testImplementation it['junit']
+ testImplementation it['robolectric']
+ testImplementation it['mockito']
+ }
+}
+
+registerArtifact(this)
\ No newline at end of file
diff --git a/markwon-simple-ext/gradle.properties b/markwon-simple-ext/gradle.properties
new file mode 100644
index 00000000..2e2ad8d5
--- /dev/null
+++ b/markwon-simple-ext/gradle.properties
@@ -0,0 +1,4 @@
+POM_NAME=Simple Extension
+POM_ARTIFACT_ID=simple-ext
+POM_DESCRIPTION=Custom extension based on simple delimiter usage
+POM_PACKAGING=aar
\ No newline at end of file
diff --git a/markwon-simple-ext/src/main/AndroidManifest.xml b/markwon-simple-ext/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..26f87af2
--- /dev/null
+++ b/markwon-simple-ext/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtBuilder.java b/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtBuilder.java
new file mode 100644
index 00000000..10d0ed95
--- /dev/null
+++ b/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtBuilder.java
@@ -0,0 +1,64 @@
+package io.noties.markwon.simple.ext;
+
+import androidx.annotation.NonNull;
+
+import org.commonmark.parser.delimiter.DelimiterProcessor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.noties.markwon.SpanFactory;
+
+// @since 4.0.0-SNAPSHOT
+class SimpleExtBuilder {
+
+ private final List extensions = new ArrayList<>(2);
+
+ private boolean isBuilt;
+
+ void addExtension(
+ int length,
+ char character,
+ @NonNull SpanFactory spanFactory) {
+
+ checkState();
+
+ extensions.add(new SimpleExtDelimiterProcessor(
+ character,
+ character,
+ length,
+ spanFactory));
+ }
+
+ void addExtension(
+ int length,
+ char openingCharacter,
+ char closingCharacter,
+ @NonNull SpanFactory spanFactory) {
+
+ checkState();
+
+ extensions.add(new SimpleExtDelimiterProcessor(
+ openingCharacter,
+ closingCharacter,
+ length,
+ spanFactory));
+ }
+
+ @NonNull
+ List build() {
+
+ checkState();
+
+ isBuilt = true;
+
+ return extensions;
+ }
+
+ private void checkState() {
+ if (isBuilt) {
+ throw new IllegalStateException("SimpleExtBuilder is already built, " +
+ "do not mutate SimpleExtPlugin after configuration is finished");
+ }
+ }
+}
diff --git a/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtDelimiterProcessor.java b/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtDelimiterProcessor.java
new file mode 100644
index 00000000..5cde82fe
--- /dev/null
+++ b/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtDelimiterProcessor.java
@@ -0,0 +1,70 @@
+package io.noties.markwon.simple.ext;
+
+import androidx.annotation.NonNull;
+
+import org.commonmark.node.Node;
+import org.commonmark.node.Text;
+import org.commonmark.parser.delimiter.DelimiterProcessor;
+import org.commonmark.parser.delimiter.DelimiterRun;
+
+import io.noties.markwon.SpanFactory;
+
+// @since 4.0.0-SNAPSHOT
+class SimpleExtDelimiterProcessor implements DelimiterProcessor {
+
+ private final char open;
+ private final char close;
+ private final int length;
+ private final SpanFactory spanFactory;
+
+ SimpleExtDelimiterProcessor(
+ char open,
+ char close,
+ int length,
+ @NonNull SpanFactory spanFactory) {
+ this.open = open;
+ this.close = close;
+ this.length = length;
+ this.spanFactory = spanFactory;
+ }
+
+ @Override
+ public char getOpeningCharacter() {
+ return open;
+ }
+
+ @Override
+ public char getClosingCharacter() {
+ return close;
+ }
+
+ @Override
+ public int getMinLength() {
+ return length;
+ }
+
+ @Override
+ public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
+ if (opener.length() >= length && closer.length() >= length) {
+ return length;
+ }
+ return 0;
+ }
+
+ @Override
+ public void process(Text opener, Text closer, int delimiterUse) {
+
+ final Node node = new SimpleExtNode(spanFactory);
+
+ Node tmp = opener.getNext();
+ Node next;
+
+ while (tmp != null && tmp != closer) {
+ next = tmp.getNext();
+ node.appendChild(tmp);
+ tmp = next;
+ }
+
+ opener.insertAfter(node);
+ }
+}
diff --git a/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtNode.java b/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtNode.java
new file mode 100644
index 00000000..d43855e2
--- /dev/null
+++ b/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtNode.java
@@ -0,0 +1,28 @@
+package io.noties.markwon.simple.ext;
+
+import androidx.annotation.NonNull;
+
+import org.commonmark.node.CustomNode;
+import org.commonmark.node.Visitor;
+
+import io.noties.markwon.SpanFactory;
+
+// @since 4.0.0-SNAPSHOT
+class SimpleExtNode extends CustomNode {
+
+ private final SpanFactory spanFactory;
+
+ SimpleExtNode(@NonNull SpanFactory spanFactory) {
+ this.spanFactory = spanFactory;
+ }
+
+ @Override
+ public void accept(Visitor visitor) {
+ visitor.visit(this);
+ }
+
+ @NonNull
+ SpanFactory spanFactory() {
+ return spanFactory;
+ }
+}
diff --git a/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtPlugin.java b/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtPlugin.java
new file mode 100644
index 00000000..48e90359
--- /dev/null
+++ b/markwon-simple-ext/src/main/java/io/noties/markwon/simple/ext/SimpleExtPlugin.java
@@ -0,0 +1,85 @@
+package io.noties.markwon.simple.ext;
+
+import androidx.annotation.NonNull;
+
+import org.commonmark.parser.Parser;
+import org.commonmark.parser.delimiter.DelimiterProcessor;
+
+import io.noties.markwon.AbstractMarkwonPlugin;
+import io.noties.markwon.MarkwonVisitor;
+import io.noties.markwon.SpanFactory;
+import io.noties.markwon.SpannableBuilder;
+
+/**
+ * @since 4.0.0-SNAPSHOT
+ */
+public class SimpleExtPlugin extends AbstractMarkwonPlugin {
+
+ public interface SimpleExtConfigure {
+ void configure(@NonNull SimpleExtPlugin plugin);
+ }
+
+ @NonNull
+ public static SimpleExtPlugin create() {
+ return new SimpleExtPlugin();
+ }
+
+ @NonNull
+ public static SimpleExtPlugin create(@NonNull SimpleExtConfigure configure) {
+ final SimpleExtPlugin plugin = new SimpleExtPlugin();
+ configure.configure(plugin);
+ return plugin;
+ }
+
+ private final SimpleExtBuilder builder = new SimpleExtBuilder();
+
+ @SuppressWarnings("WeakerAccess")
+ SimpleExtPlugin() {
+ }
+
+ @NonNull
+ public SimpleExtPlugin addExtension(
+ int length,
+ char character,
+ @NonNull SpanFactory spanFactory) {
+ builder.addExtension(length, character, spanFactory);
+ return this;
+ }
+
+ @NonNull
+ public SimpleExtPlugin addExtension(
+ int length,
+ char openingCharacter,
+ char closingCharacter,
+ @NonNull SpanFactory spanFactory) {
+ builder.addExtension(length, openingCharacter, closingCharacter, spanFactory);
+ return this;
+ }
+
+ @Override
+ public void configureParser(@NonNull Parser.Builder builder) {
+ for (DelimiterProcessor processor : this.builder.build()) {
+ builder.customDelimiterProcessor(processor);
+ }
+ }
+
+ @Override
+ public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
+ builder.on(SimpleExtNode.class, new MarkwonVisitor.NodeVisitor() {
+ @Override
+ public void visit(@NonNull MarkwonVisitor visitor, @NonNull SimpleExtNode simpleExtNode) {
+
+ final int length = visitor.length();
+
+ visitor.visitChildren(simpleExtNode);
+
+ SpannableBuilder.setSpans(
+ visitor.builder(),
+ simpleExtNode.spanFactory().getSpans(visitor.configuration(), visitor.renderProps()),
+ length,
+ visitor.length()
+ );
+ }
+ });
+ }
+}
diff --git a/sample/build.gradle b/sample/build.gradle
index f624824b..c3ff21dd 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -43,6 +43,7 @@ dependencies {
implementation project(':markwon-syntax-highlight')
implementation project(':markwon-recycler')
implementation project(':markwon-recycler-table')
+ implementation project(':markwon-simple-ext')
implementation project(':markwon-image-picasso')
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 22ebc86b..7018e3fc 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -25,6 +25,7 @@
+
diff --git a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java
index 8c197ea7..a27d37a9 100644
--- a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java
+++ b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java
@@ -24,6 +24,7 @@ import io.noties.markwon.sample.customextension.CustomExtensionActivity;
import io.noties.markwon.sample.html.HtmlActivity;
import io.noties.markwon.sample.latex.LatexActivity;
import io.noties.markwon.sample.recycler.RecyclerActivity;
+import io.noties.markwon.sample.simpleext.SimpleExtActivity;
public class MainActivity extends Activity {
@@ -102,6 +103,10 @@ public class MainActivity extends Activity {
activity = HtmlActivity.class;
break;
+ case SIMPLE_EXT:
+ activity = SimpleExtActivity.class;
+ break;
+
default:
throw new IllegalStateException("No Activity is associated with sample-item: " + item);
}
diff --git a/sample/src/main/java/io/noties/markwon/sample/Sample.java b/sample/src/main/java/io/noties/markwon/sample/Sample.java
index 03160a35..dbe9bc8a 100644
--- a/sample/src/main/java/io/noties/markwon/sample/Sample.java
+++ b/sample/src/main/java/io/noties/markwon/sample/Sample.java
@@ -15,7 +15,9 @@ public enum Sample {
RECYCLER(R.string.sample_recycler),
- HTML(R.string.sample_html);
+ HTML(R.string.sample_html),
+
+ SIMPLE_EXT(R.string.sample_simple_ext);
private final int textResId;
diff --git a/sample/src/main/java/io/noties/markwon/sample/simpleext/SimpleExtActivity.java b/sample/src/main/java/io/noties/markwon/sample/simpleext/SimpleExtActivity.java
new file mode 100644
index 00000000..77902e16
--- /dev/null
+++ b/sample/src/main/java/io/noties/markwon/sample/simpleext/SimpleExtActivity.java
@@ -0,0 +1,43 @@
+package io.noties.markwon.sample.simpleext;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.text.style.ForegroundColorSpan;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import io.noties.markwon.Markwon;
+import io.noties.markwon.core.spans.EmphasisSpan;
+import io.noties.markwon.core.spans.StrongEmphasisSpan;
+import io.noties.markwon.sample.R;
+import io.noties.markwon.simple.ext.SimpleExtPlugin;
+
+public class SimpleExtActivity extends Activity {
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_text_view);
+
+ final TextView textView = findViewById(R.id.text_view);
+
+ final Markwon markwon = Markwon.builder(this)
+ .usePlugin(SimpleExtPlugin.create(plugin -> plugin
+ // +sometext+
+ .addExtension(1, '+', (_1, _2) -> new EmphasisSpan())
+ // ??sometext??
+ .addExtension(2, '?', (_1, _2) -> new StrongEmphasisSpan())
+ // @@sometext$$
+ .addExtension(2, '@', '$', (_1, _2) -> new ForegroundColorSpan(Color.RED))))
+ .build();
+
+ final String markdown = "# SimpleExt\n" +
+ "\n" +
+ "+let's start with `+`, ??then we can use this, and finally @@this$$??+";
+
+ markwon.setMarkdown(textView, markdown);
+ }
+}
diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml
index 24cab950..a624f921 100644
--- a/sample/src/main/res/values/strings-samples.xml
+++ b/sample/src/main/res/values/strings-samples.xml
@@ -17,4 +17,7 @@
# \# Html\n\nShows how to define own tag handlers
+ # \# SimpleExt\n\nShows how to use SimpleExtPlugin module
+ to create own delimited parser extensions
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 38372eba..45a92d52 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -12,5 +12,6 @@ include ':app', ':sample',
':markwon-linkify',
':markwon-recycler',
':markwon-recycler-table',
+ ':markwon-simple-ext',
':markwon-syntax-highlight',
':markwon-test-span'