Better codeblock support

Stephen Paul Weber created

Including basic support for language tagging

Change summary

src/cheogram/java/com/cheogram/android/SpannedToXHTML.java    | 15 +++-
src/main/java/eu/siacs/conversations/utils/ImStyleParser.java |  1 
src/main/java/eu/siacs/conversations/utils/StylingHelper.java | 15 ++++
3 files changed, 25 insertions(+), 6 deletions(-)

Detailed changes

src/cheogram/java/com/cheogram/android/SpannedToXHTML.java 🔗

@@ -78,6 +78,7 @@ public class SpannedToXHTML {
 	}
 
 	private static void withinParagraph(Element outer, Spanned text, int start, int end) {
+		String removed = "";
 		int next;
 		outer:
 		for (int i = start; i < end; i = next) {
@@ -85,8 +86,9 @@ public class SpannedToXHTML {
 			next = text.nextSpanTransition(i, end, CharacterStyle.class);
 			CharacterStyle[] style = text.getSpans(i, next, CharacterStyle.class);
 			for (int j = 0; j < style.length; j++) {
-				final var userFlags = (text.getSpanFlags(style[j]) & Spanned.SPAN_USER) >> Spanned.SPAN_USER_SHIFT;
+				final var userFlags = ((text.getSpanFlags(style[j]) & Spanned.SPAN_USER) >> Spanned.SPAN_USER_SHIFT) & 0xf;
 				if (userFlags == StylingHelper.XHTML_REMOVE) {
+					removed = text.subSequence(i, next).toString();
 					continue outer;
 				}
 
@@ -108,9 +110,14 @@ public class SpannedToXHTML {
 					}
 				}
 				if (style[j] instanceof TypefaceSpan) {
-					String s = ((TypefaceSpan) style[j]).getFamily();
-					if ("monospace".equals(s)) {
-						out = out.addChild("tt");
+					if (userFlags == StylingHelper.XHTML_CODE) {
+						out = out.addChild("code");
+						if (removed.length() > 3) out.setAttribute("class", "language-" + removed.substring(3).trim());
+					} else {
+						String s = ((TypefaceSpan) style[j]).getFamily();
+						if ("monospace".equals(s)) {
+							out = out.addChild("tt");
+						}
 					}
 				}
 				if (style[j] instanceof SuperscriptSpan) {

src/main/java/eu/siacs/conversations/utils/StylingHelper.java 🔗

@@ -67,6 +67,7 @@ public class StylingHelper {
 	public static final int XHTML_IGNORE = 1;
 	public static final int XHTML_REMOVE = 2;
 	public static final int XHTML_EMPHASIS = 3;
+	public static final int XHTML_CODE = 4;
 	public static final int NOLINKIFY = 0xf0;
 
 	private static final List<? extends Class<? extends ParcelableSpan>> SPAN_CLASSES = Arrays.asList(
@@ -89,15 +90,25 @@ public class StylingHelper {
 	public static void format(final Editable editable, int start, int end, @ColorInt int textColor, final boolean composing) {
 		for (ImStyleParser.Style style : ImStyleParser.parse(editable, start, end)) {
 			final int keywordLength = style.getKeyword().length();
+			int keywordLengthStart = keywordLength;
+			if ("```".equals(style.getKeyword())) {
+				int i;
+				for (i = style.getStart(); i < editable.length(); i++) {
+					if (editable.charAt(i) == '\n') break;
+				}
+				keywordLengthStart = i - style.getStart() + 1;
+			}
+
 			editable.setSpan(
 				createSpanForStyle(style),
-				style.getStart() + keywordLength,
+				style.getStart() + keywordLengthStart,
 				style.getEnd() - keywordLength + 1,
 				Spanned.SPAN_EXCLUSIVE_EXCLUSIVE |
 					("*".equals(style.getKeyword()) || "_".equals(style.getKeyword()) ? XHTML_EMPHASIS << Spanned.SPAN_USER_SHIFT : 0) |
+					("```".equals(style.getKeyword()) && keywordLengthStart > 4 ? XHTML_CODE << Spanned.SPAN_USER_SHIFT : 0) |
 					("`".equals(style.getKeyword()) || "```".equals(style.getKeyword()) ? NOLINKIFY << Spanned.SPAN_USER_SHIFT : 0)
 			);
-			makeKeywordOpaque(editable, style.getStart(), style.getStart() + keywordLength + ("```".equals(style.getKeyword()) ? 1 : 0), textColor, composing);
+			makeKeywordOpaque(editable, style.getStart(), style.getStart() + keywordLengthStart, textColor, composing);
 			makeKeywordOpaque(editable, style.getEnd() - keywordLength + 1, style.getEnd() + 1, textColor, composing);
 		}
 	}