From 417a3f48e359e17f141174451df426b2a7ebad32 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sun, 8 Sep 2024 13:55:18 -0500 Subject: [PATCH] Better codeblock support Including basic support for language tagging --- .../java/com/cheogram/android/SpannedToXHTML.java | 15 +++++++++++---- .../siacs/conversations/utils/ImStyleParser.java | 1 + .../siacs/conversations/utils/StylingHelper.java | 15 +++++++++++++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/cheogram/java/com/cheogram/android/SpannedToXHTML.java b/src/cheogram/java/com/cheogram/android/SpannedToXHTML.java index 1eb13582397b167b605742c6235efe6046e22c04..087bbc54c6cf95c659d5a512aec726a5536a50a7 100644 --- a/src/cheogram/java/com/cheogram/android/SpannedToXHTML.java +++ b/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) { diff --git a/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java b/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java index d21bfadf133857fd0f15d359e812c503c441e671..096c091e18510938309fb1af0dcd20fe0e0614e0 100644 --- a/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java +++ b/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java @@ -58,6 +58,7 @@ public class ImStyleParser { i = to; continue; } + continue; } int to = seekEnd(text, c, i + 1, end); if (to != -1 && (to != i + 1 || ALLOW_EMPTY)) { diff --git a/src/main/java/eu/siacs/conversations/utils/StylingHelper.java b/src/main/java/eu/siacs/conversations/utils/StylingHelper.java index 46449b76d3ab6fbe0bcbdd428ed450e8ac982685..1f6c1b0d9d9e27b0590afff25b7c645c0aa97b12 100644 --- a/src/main/java/eu/siacs/conversations/utils/StylingHelper.java +++ b/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> 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); } }