Superscript parser overhaul (#1162)

* Superscript parser overhaul

* Make superscript work with tables

* Fix some issues with Table compatibility

* Some bug fixes

* Re-enable Autolink
This commit is contained in:
scria1000 2022-11-03 13:07:25 +03:00 committed by GitHub
parent e362f2ecf0
commit 20214ce323
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 453 additions and 178 deletions

View File

@ -161,7 +161,6 @@ dependencies {
implementation "io.noties.markwon:linkify:$markwonVersion"
implementation "io.noties.markwon:recycler-table:$markwonVersion"
implementation "io.noties.markwon:simple-ext:$markwonVersion"
implementation "io.noties.markwon:html:$markwonVersion"
implementation "io.noties.markwon:inline-parser:$markwonVersion"
implementation 'com.atlassian.commonmark:commonmark-ext-gfm-tables:0.14.0'
implementation 'me.saket:better-link-movement-method:2.2.0'

View File

@ -178,12 +178,6 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
binding.commentContentMarkdownView.setNestedScrollingEnabled(false);
int linkColor = mCustomThemeWrapper.getLinkColor();
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return Utils.fixSuperScript(markdown);
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
if (contentTypeface != null) {

View File

@ -117,12 +117,6 @@ public class FullMarkdownActivity extends BaseActivity {
int spoilerBackgroundColor = markdownColor | 0xFF000000;
int linkColor = mCustomThemeWrapper.getLinkColor();
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return Utils.fixSuperScript(markdown);
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
if (typeface != null) {

View File

@ -149,12 +149,6 @@ public class WikiActivity extends BaseActivity {
int spoilerBackgroundColor = markdownColor | 0xFF000000;
int linkColor = mCustomThemeWrapper.getLinkColor();
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return Utils.fixSuperScript(markdown);
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
textView.setTextColor(markdownColor);

View File

@ -138,12 +138,6 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter<Comment
mCommentIconAndInfoColor = customThemeWrapper.getCommentIconAndInfoColor();
int linkColor = customThemeWrapper.getLinkColor();
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return Utils.fixSuperScript(markdown);
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
if (mActivity.contentTypeface != null) {

View File

@ -169,12 +169,6 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi
int commentSpoilerBackgroundColor = mCommentTextColor | 0xFF000000;
int linkColor = customThemeWrapper.getLinkColor();
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return Utils.fixSuperScript(markdown);
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
if (mActivity.contentTypeface != null) {

View File

@ -28,8 +28,6 @@ import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.html.tag.SuperScriptHandler;
import io.noties.markwon.inlineparser.AutolinkInlineProcessor;
import io.noties.markwon.inlineparser.BangInlineProcessor;
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
@ -47,11 +45,10 @@ import ml.docilealligator.infinityforreddit.events.ChangeInboxCountEvent;
import ml.docilealligator.infinityforreddit.markdown.RedditHeadingPlugin;
import ml.docilealligator.infinityforreddit.markdown.SpoilerAwareMovementMethod;
import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin;
import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.markdown.SuperscriptPlugin;
import ml.docilealligator.infinityforreddit.message.FetchMessage;
import ml.docilealligator.infinityforreddit.message.Message;
import ml.docilealligator.infinityforreddit.message.ReadMessage;
import ml.docilealligator.infinityforreddit.utils.Utils;
import retrofit2.Retrofit;
public class MessageRecyclerViewAdapter extends PagedListAdapter<Message, RecyclerView.ViewHolder> {
@ -109,21 +106,10 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter<Message, Recycl
// add tables support and replace with MarkdownUtils#commonPostMarkwonBuilder
mMarkwon = Markwon.builder(mActivity)
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
plugin.excludeInlineProcessor(AutolinkInlineProcessor.class);
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
plugin.excludeInlineProcessor(BangInlineProcessor.class);
plugin.addInlineProcessor(new SuperscriptInlineProcessor());
}))
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return Utils.fixSuperScript(markdown);
}
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver((view, link) -> {
@ -139,6 +125,7 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter<Message, Recycl
builder.linkColor(customThemeWrapper.getLinkColor());
}
})
.usePlugin(SuperscriptPlugin.create())
.usePlugin(SpoilerParserPlugin.create(mSecondaryTextColor, spoilerBackgroundColor))
.usePlugin(RedditHeadingPlugin.create())
.usePlugin(StrikethroughPlugin.create())

View File

@ -232,12 +232,6 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler
int postSpoilerBackgroundColor = markdownColor | 0xFF000000;
int linkColor = customThemeWrapper.getLinkColor();
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return Utils.fixSuperScript(markdown);
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
if (mActivity.contentTypeface != null) {

View File

@ -32,8 +32,6 @@ import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.html.tag.SuperScriptHandler;
import io.noties.markwon.inlineparser.AutolinkInlineProcessor;
import io.noties.markwon.inlineparser.BangInlineProcessor;
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
@ -49,7 +47,7 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.markdown.RedditHeadingPlugin;
import ml.docilealligator.infinityforreddit.markdown.SpoilerAwareMovementMethod;
import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin;
import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.markdown.SuperscriptPlugin;
import ml.docilealligator.infinityforreddit.message.Message;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.Utils;
@ -85,21 +83,10 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
// add tables support and replace with MarkdownUtils#commonPostMarkwonBuilder
mMarkwon = Markwon.builder(viewPrivateMessagesActivity)
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
plugin.excludeInlineProcessor(AutolinkInlineProcessor.class);
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
plugin.excludeInlineProcessor(BangInlineProcessor.class);
plugin.addInlineProcessor(new SuperscriptInlineProcessor());
}))
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return Utils.fixSuperScript(markdown);
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
if (mViewPrivateMessagesActivity.contentTypeface != null) {
@ -122,6 +109,7 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
builder.linkColor(customThemeWrapper.getLinkColor());
}
})
.usePlugin(SuperscriptPlugin.create())
.usePlugin(StrikethroughPlugin.create())
.usePlugin(SpoilerParserPlugin.create(commentColor, commentColor | 0xFF000000))
.usePlugin(RedditHeadingPlugin.create())

View File

@ -53,12 +53,6 @@ public class RulesRecyclerViewAdapter extends RecyclerView.Adapter<RulesRecycler
mPrimaryTextColor = customThemeWrapper.getPrimaryTextColor();
int spoilerBackgroundColor = mPrimaryTextColor | 0xFF000000;
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return Utils.fixSuperScript(markdown);
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
if (activity.typeface != null) {

View File

@ -104,12 +104,6 @@ public class SidebarFragment extends Fragment {
int spoilerBackgroundColor = markdownColor | 0xFF000000;
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return Utils.fixSuperScript(markdown);
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
if (activity.contentTypeface != null) {

View File

@ -11,8 +11,6 @@ import org.commonmark.ext.gfm.tables.TableBlock;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonPlugin;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.html.tag.SuperScriptHandler;
import io.noties.markwon.inlineparser.AutolinkInlineProcessor;
import io.noties.markwon.inlineparser.BangInlineProcessor;
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
@ -39,15 +37,11 @@ public class MarkdownUtils {
@Nullable BetterLinkMovementMethod.OnLinkLongClickListener onLinkLongClickListener) {
return Markwon.builder(context)
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
plugin.excludeInlineProcessor(AutolinkInlineProcessor.class);
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
plugin.excludeInlineProcessor(BangInlineProcessor.class);
plugin.addInlineProcessor(new SuperscriptInlineProcessor());
}))
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(miscPlugin)
.usePlugin(SuperscriptPlugin.create())
.usePlugin(SpoilerParserPlugin.create(markdownColor, spoilerBackgroundColor))
.usePlugin(RedditHeadingPlugin.create())
.usePlugin(StrikethroughPlugin.create())
@ -68,7 +62,6 @@ public class MarkdownUtils {
@Nullable BetterLinkMovementMethod.OnLinkLongClickListener onLinkLongClickListener) {
return Markwon.builder(context)
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
plugin.excludeInlineProcessor(AutolinkInlineProcessor.class);
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
plugin.excludeInlineProcessor(BangInlineProcessor.class);
}))

View File

@ -0,0 +1,21 @@
package ml.docilealligator.infinityforreddit.markdown;
import org.commonmark.node.CustomNode;
import org.commonmark.node.Visitor;
public class Superscript extends CustomNode {
private boolean isBracketed;
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public boolean isBracketed() {
return isBracketed;
}
public void setBracketed(boolean bracketed) {
isBracketed = bracketed;
}
}

View File

@ -0,0 +1,51 @@
package ml.docilealligator.infinityforreddit.markdown;
import static io.noties.markwon.inlineparser.InlineParserUtils.mergeChildTextNodes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import io.noties.markwon.inlineparser.InlineProcessor;
public class SuperscriptClosingInlineProcessor extends InlineProcessor {
@NonNull
private final SuperscriptOpeningStorage superscriptOpeningStorage;
public SuperscriptClosingInlineProcessor(@NonNull SuperscriptOpeningStorage superscriptOpeningStorage) {
this.superscriptOpeningStorage = superscriptOpeningStorage;
}
@Override
public char specialCharacter() {
return ')';
}
@Nullable
@Override
protected Node parse() {
SuperscriptOpeningBracket superscriptOpening = superscriptOpeningStorage.pop(block);
if (superscriptOpening == null) {
return null;
}
index++;
Superscript superscript = new Superscript();
superscript.setBracketed(true);
Node node = superscriptOpening.node.getNext();
while (node != null) {
Node next = node.getNext();
superscript.appendChild(node);
node = next;
}
// Process delimiters such as emphasis inside spoiler
processDelimiters(superscriptOpening.previousDelimiter);
mergeChildTextNodes(superscript);
// We don't need the corresponding text node anymore, we turned it into a spoiler node
superscriptOpening.node.unlink();
return superscript;
}
}

View File

@ -1,32 +0,0 @@
package ml.docilealligator.infinityforreddit.markdown;
import androidx.annotation.Nullable;
import org.commonmark.node.HtmlInline;
import org.commonmark.node.Node;
import java.util.regex.Pattern;
import io.noties.markwon.inlineparser.InlineProcessor;
public class SuperscriptInlineProcessor extends InlineProcessor {
private static final Pattern HTML_TAG = Pattern.compile("^</?sup>", Pattern.CASE_INSENSITIVE);
@Override
public char specialCharacter() {
return '<';
}
@Nullable
@Override
protected Node parse() {
String m = match(HTML_TAG);
if (m != null) {
HtmlInline node = new HtmlInline();
node.setLiteral(m);
return node;
} else {
return null;
}
}
}

View File

@ -0,0 +1,17 @@
package ml.docilealligator.infinityforreddit.markdown;
import org.commonmark.node.Node;
public class SuperscriptOpening {
/**
* Node that contains non-bracketed superscript opening markdown ({@code ^}).
*/
public final Node node;
public final Integer start;
public SuperscriptOpening(Node node, int start) {
this.node = node;
this.start = start;
}
}

View File

@ -0,0 +1,31 @@
package ml.docilealligator.infinityforreddit.markdown;
import org.commonmark.internal.Delimiter;
import org.commonmark.node.Node;
public class SuperscriptOpeningBracket {
/**
* Node that contains superscript opening bracket markdown ({@code ^(}).
*/
public final Node node;
/**
* Previous superscript opening bracket.
*/
public final SuperscriptOpeningBracket previous;
/**
* Previous delimiter (emphasis, etc) before this bracket.
*/
public final Delimiter previousDelimiter;
public final Integer start;
public SuperscriptOpeningBracket(Node node, SuperscriptOpeningBracket previous, Delimiter previousDelimiter) {
this.node = node;
this.previous = previous;
this.previousDelimiter = previousDelimiter;
this.start = null;
}
}

View File

@ -0,0 +1,50 @@
package ml.docilealligator.infinityforreddit.markdown;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import org.commonmark.node.Text;
import io.noties.markwon.inlineparser.InlineProcessor;
public class SuperscriptOpeningInlineProcessor extends InlineProcessor {
@NonNull
private final SuperscriptOpeningStorage superscriptOpeningStorage;
public SuperscriptOpeningInlineProcessor(@NonNull SuperscriptOpeningStorage superscriptOpeningStorage) {
this.superscriptOpeningStorage = superscriptOpeningStorage;
}
@Override
public char specialCharacter() {
return '^';
}
@Nullable
@Override
protected Node parse() {
index++;
char c = peek();
if (c != '\0' && !Character.isWhitespace(c)) {
if (c == '(') {
index++;
Text node = text("^(");
superscriptOpeningStorage.add(block, node, lastDelimiter());
return node;
}
if (lastDelimiter() != null && lastDelimiter().canOpen && block.getLastChild() != null) {
if (lastDelimiter().node == this.block.getLastChild()) {
if (lastDelimiter().delimiterChar == peek()) {
index--;
return null;
}
}
}
return new Superscript();
}
return null;
}
}

View File

@ -0,0 +1,38 @@
package ml.docilealligator.infinityforreddit.markdown;
import androidx.annotation.Nullable;
import org.commonmark.internal.Delimiter;
import org.commonmark.node.Node;
public class SuperscriptOpeningStorage {
@Nullable
private SuperscriptOpeningBracket lastBracket;
private Node currentBlock;
public void clear() {
lastBracket = null;
}
public void add(Node block, Node node, Delimiter lastDelimiter) {
updateBlock(block);
lastBracket = new SuperscriptOpeningBracket(node, lastBracket, lastDelimiter);
}
@Nullable
public SuperscriptOpeningBracket pop(Node block) {
updateBlock(block);
SuperscriptOpeningBracket opening = lastBracket;
if (opening != null) {
lastBracket = opening.previous;
}
return opening;
}
private void updateBlock(Node block) {
if (block != currentBlock) {
clear();
}
currentBlock = block;
}
}

View File

@ -0,0 +1,206 @@
package ml.docilealligator.infinityforreddit.markdown;
import android.text.Spannable;
import android.text.Spanned;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.ext.gfm.tables.TableCell;
import org.commonmark.node.Node;
import org.commonmark.node.Paragraph;
import java.util.ArrayList;
import java.util.List;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.MarkwonSpansFactory;
import io.noties.markwon.MarkwonVisitor;
import io.noties.markwon.core.spans.CodeSpan;
import io.noties.markwon.core.spans.TextViewSpan;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
public class SuperscriptPlugin extends AbstractMarkwonPlugin {
private final SuperscriptOpeningStorage superscriptOpeningBracketStorage;
private final List<SuperscriptOpening> superscriptOpeningList;
SuperscriptPlugin() {
this.superscriptOpeningBracketStorage = new SuperscriptOpeningStorage();
this.superscriptOpeningList = new ArrayList<>();
}
@NonNull
public static SuperscriptPlugin create() {
return new SuperscriptPlugin();
}
private static char peek(int index, CharSequence input) {
return index >= 0 && index < input.length() ? input.charAt(index) : '\0';
}
private static List<SpanInfo> getSpans(Spannable spannable, int start, int end) {
var spanArray = spannable.getSpans(start, end, Object.class);
List<SpanInfo> spanList = new ArrayList<>();
for (int i = spanArray.length - 1; i >= 0; i--) {
Object span = spanArray[i];
int spanStart = spannable.getSpanStart(span);
int spanEnd = spannable.getSpanEnd(span);
int spanFlags = spannable.getSpanFlags(span);
spanList.add(new SpanInfo(span, spanStart, spanEnd, spanFlags));
}
return spanList;
}
private static SpanInfo matchSuperscriptAtPosition(List<SpanInfo> spans, int value) {
for (var span : spans)
if (span.what.getClass() == SuperscriptSpan.class && !((SuperscriptSpan) span.what).isBracketed && span.start <= value && value <= span.end)
return span;
return null;
}
private static SpanInfo matchSpanAtPosition(List<SpanInfo> spans, int value, Object spanClass) {
for (var span : spans)
if (span.what.getClass() == spanClass && span.start <= value && value <= span.end)
return span;
return null;
}
private static SpanInfo matchNonTextSpanAtBoundary(List<SpanInfo> spans, int value) {
for (var span : spans)
if ((span.end == value || span.start == value) && span.what.getClass() != CodeSpan.class && span.what.getClass() != SuperscriptSpan.class && span.what.getClass() != TextViewSpan.class)
return span;
return null;
}
@Override
public void configure(@NonNull Registry registry) {
registry.require(MarkwonInlineParserPlugin.class, plugin -> {
plugin.factoryBuilder().addInlineProcessor(new SuperscriptOpeningInlineProcessor(superscriptOpeningBracketStorage));
plugin.factoryBuilder().addInlineProcessor(new SuperscriptClosingInlineProcessor(superscriptOpeningBracketStorage));
}
);
}
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
builder.setFactory(Superscript.class, (config, renderProps) -> new SuperscriptSpan(true));
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(Superscript.class, new MarkwonVisitor.NodeVisitor<>() {
int depth = 0;
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Superscript superscript) {
int start = visitor.length();
if (!superscript.isBracketed()) {
visitor.builder().setSpan(new SuperscriptSpan(false), start, start + 1); // Workaround for Table Plugin
superscriptOpeningList.add(new SuperscriptOpening(superscript, start));
return;
}
depth++;
visitor.visitChildren(superscript);
depth--;
if (depth == 0) {
int end = visitor.builder().length();
var spans = visitor.builder().getSpans(start, end);
for (var span : spans) {
if (span.what instanceof CodeSpan) {
if (span.end <= end) {
visitor.builder().setSpan(new SuperscriptSpan(true), start, span.start);
}
start = span.end;
}
}
if (start < end) {
visitor.setSpansForNode(superscript, start);
}
}
}
});
}
@Override
public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
superscriptOpeningBracketStorage.clear();
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
if (superscriptOpeningList.size() == 0 || !(markdown instanceof Spannable)) {
return;
}
var spannable = (Spannable) markdown;
var spans = getSpans(spannable, 0, spannable.length());
final String text = spannable.toString();
outerLoop:
for (int i = 0; i < superscriptOpeningList.size(); i++) {
SuperscriptOpening opening = superscriptOpeningList.get(i);
SuperscriptOpening nextOpening = i + 1 < superscriptOpeningList.size() ? superscriptOpeningList.get(i + 1) : null;
// Workaround for Table Plugin
var superscriptMarker = matchSuperscriptAtPosition(spans, opening.start);
if (superscriptMarker == null)
return;
spannable.removeSpan(superscriptMarker.what);
spans.remove(superscriptMarker);
boolean isNextOpeningOfLocalNode = nextOpening != null && opening.node.getParent().equals(nextOpening.node.getParent());
if (opening.start >= text.length() || (matchSpanAtPosition(spans, opening.start, CodeSpan.class) == null && Character.isWhitespace(text.charAt(opening.start))) || (isNextOpeningOfLocalNode && opening.start.equals(nextOpening.start))) {
superscriptOpeningList.remove(i);
i--;
continue;
}
boolean isChildOfDelimited = !(opening.node.getParent() == null || opening.node.getParent() instanceof Paragraph || opening.node.getParent() instanceof TableCell);
int openingStart = opening.start;
for (int j = opening.start; j <= text.length(); j++) {
char currentChar = peek(j, text);
SpanInfo codeSpanAtPosition = matchSpanAtPosition(spans, j, CodeSpan.class);
SpanInfo nonTextSpanAtBoundary = matchNonTextSpanAtBoundary(spans, j);
// When we reach the end position of, for example, an Emphasis
// Check whether the superscript originated from inside this Emphasis
// If so, stop further spanning of the current Superscript
boolean isInsideDelimited = nonTextSpanAtBoundary != null && openingStart != j && j == nonTextSpanAtBoundary.end && (openingStart > nonTextSpanAtBoundary.start || isChildOfDelimited);
if (codeSpanAtPosition != null) {
if (openingStart < j) {
spannable.setSpan(new SuperscriptSpan(false), openingStart, j, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
//Skip to end of CodeSpan
j = codeSpanAtPosition.end;
currentChar = peek(j, text);
if (currentChar == '\0' || Character.isWhitespace(currentChar) || (isNextOpeningOfLocalNode && j == nextOpening.start) || isInsideDelimited) {
superscriptOpeningList.remove(i);
i--;
continue outerLoop;
}
openingStart = j;
} else if (currentChar == '\0' || Character.isWhitespace(currentChar) || isInsideDelimited) {
spannable.setSpan(new SuperscriptSpan(false), openingStart, j, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
superscriptOpeningList.remove(i);
i--;
continue outerLoop;
}
}
}
}
private static class SpanInfo {
public final Object what;
public final int start;
public final int end;
public final int flags;
private SpanInfo(Object what, int start, int end, int flags) {
this.what = what;
this.start = start;
this.end = end;
this.flags = flags;
}
}
}

View File

@ -0,0 +1,34 @@
package ml.docilealligator.infinityforreddit.markdown;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
import androidx.annotation.NonNull;
public class SuperscriptSpan extends MetricAffectingSpan {
private static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F;
public final boolean isBracketed;
public SuperscriptSpan() {
this.isBracketed = false;
}
public SuperscriptSpan(boolean isBracketed) {
this.isBracketed = isBracketed;
}
@Override
public void updateDrawState(TextPaint tp) {
apply(tp);
}
@Override
public void updateMeasureState(@NonNull TextPaint tp) {
apply(tp);
}
private void apply(TextPaint paint) {
paint.setTextSize(paint.getTextSize() * SCRIPT_DEF_TEXT_SIZE_RATIO);
paint.baselineShift += (int) (paint.ascent() / 2);
}
}

View File

@ -83,69 +83,9 @@ public final class Utils {
regexed = REGEX_PATTERNS[1].matcher(regexed).replaceAll("[$0](https://www.reddit.com/$0)");
regexed = REGEX_PATTERNS[2].matcher(regexed).replaceAll("^");
//return fixSuperScript(regexed);
// We don't want to fix super scripts here because we need the original markdown later for editing posts
return regexed;
}
public static String fixSuperScript(String regexedMarkdown) {
StringBuilder regexed = new StringBuilder(regexedMarkdown);
boolean hasBracket = false;
int nCarets = 0;
int newLines = 0;
for (int i = 0; i < regexed.length(); i++) {
char currentChar = regexed.charAt(i);
if (hasBracket && currentChar == '\n') {
newLines++;
if (newLines > 1) {
hasBracket = false;
nCarets = 0;
newLines = 0;
}
} else if (currentChar == '^') {
if (!(i > 0 && regexed.charAt(i - 1) == '\\')) {
if (nCarets == 0 && i < regexed.length() - 1 && regexed.charAt(i + 1) == '(') {
regexed.replace(i, i + 2, "<sup>");
hasBracket = true;
} else {
regexed.replace(i, i + 1, "<sup>");
}
nCarets++;
}
} else if (hasBracket && currentChar == ')') {
if (i > 0 && regexed.charAt(i - 1) == '\\') {
hasBracket = false;
nCarets--;
continue;
}
hasBracket = false;
regexed.replace(i, i + 1, "</sup>");
nCarets--;
} else if (!hasBracket && currentChar == '\n') {
for (int j = 0; j < nCarets; j++) {
regexed.insert(i, "</sup>");
i += 6;
}
nCarets = 0;
} else if (!hasBracket && Character.isWhitespace(currentChar)) {
for (int j = 0; j < nCarets; j++) {
regexed.insert(i, "</sup>");
i += 6;
}
nCarets = 0;
} else {
newLines = 0;
}
}
if (!hasBracket) {
for (int j = 0; j < nCarets; j++) {
regexed.append("</sup>");
}
}
return regexed.toString();
}
public static String parseInlineGifInComments(String markdown) {
StringBuilder markdownStringBuilder = new StringBuilder(markdown);
Pattern inlineGifPattern = REGEX_PATTERNS[3];