git » sdk » commit 00f2c43

Support code language

author Stephen Paul Weber
2026-04-12 00:33:34 UTC
committer Stephen Paul Weber
2026-04-12 00:33:34 UTC
parent 371077331af25ae70ce5d9764b78ec6031ad7d3f

Support code language

XEP0393 doesn't specify it, but allows for it on purpose

borogove/XEP0393.hx +29 -6
test/TestXEP0393.hx +15 -3

diff --git a/borogove/XEP0393.hx b/borogove/XEP0393.hx
index 4ad6c7a..b97c7cf 100644
--- a/borogove/XEP0393.hx
+++ b/borogove/XEP0393.hx
@@ -2,6 +2,7 @@ package borogove;
 
 import borogove.Autolink;
 import borogove.Stanza;
+using StringTools;
 using borogove.Util;
 
 class XEP0393 {
@@ -27,7 +28,15 @@ class XEP0393 {
 		final s = new StringBuf();
 
 		if (xhtml.name == "pre") {
-			s.add("\n```\n");
+			final code = xhtml.getChild("code");
+			var lang = "";
+			if (code != null) {
+				final className = code.attr.get("class") ?? "";
+				if (className.startsWith("language-")) {
+					lang = className.substr(9);
+				}
+			}
+			s.add("\n```" + lang + "\n");
 		}
 
 		if (xhtml.name == "b" || xhtml.name == "strong") {
@@ -221,30 +230,44 @@ class XEP0393 {
 
 	public static function parsePreformatted(styled: UnicodeString) {
 		final lines = [];
-		var line = null;
+		var line = "";
+		var lang = null;
 		var end = 0;
 		final styledLength = styled.length;
 		while (end < styledLength) {
 			while (end < styledLength && styled.charAt(end) != "\n") {
-				if (line != null) line += styled.charAt(end);
+				line += styled.charAt(end);
 				end++;
 			}
 			if (end < styledLength && styled.charAt(end) == "\n") {
 				end++;
 			}
-			if (line != null) lines.push(line+"\n");
+
+			if (lang == null) {
+				lang = line.substr(3).trim();
+			} else {
+				lines.push(line + "\n");
+			}
 			line = "";
+
 			if (styled.substr(end, 4) == "```\n" || styled.substr(end) == "```") {
 				end += 4;
 				break;
 			}
 		}
 
-		return { block: new Stanza("pre").text(lines.join("")), rest: styled.substr(end) };
+		final block = new Stanza("pre");
+		if (lang != "") {
+			block.tag("code", {"class": 'language-$lang'}).text(lines.join(""));
+		} else {
+			block.text(lines.join(""));
+		}
+
+		return { block: block, rest: styled.substr(end) };
 	}
 
 	private static function isSpace(s: UnicodeString, pos: Int) {
 		// The version in StringTools won't use UnicodeString-aware indices
-		return StringTools.isSpace(s.charAt(pos), 0);
+		return s.charAt(pos).isSpace(0);
 	}
 }
diff --git a/test/TestXEP0393.hx b/test/TestXEP0393.hx
index 6698278..4e6df20 100644
--- a/test/TestXEP0393.hx
+++ b/test/TestXEP0393.hx
@@ -20,10 +20,22 @@ as spans* may not escape blocks.")
 		);
 	}
 
-	public function testPreformattedBlock() {
+	public function testPreformattedBlockSimple() {
 		Assert.equals(
 			"<pre>(println \"Hello, world!\")
 </pre><div/><div>This should show up as monospace, preformatted text ⤴</div>",
+			toHtml("```
+(println \"Hello, world!\")
+```
+
+This should show up as monospace, preformatted text ⤴")
+		);
+	}
+
+	public function testPreformattedBlock() {
+		Assert.equals(
+			"<pre><code class=\"language-ignored\">(println \"Hello, world!\")
+</code></pre><div/><div>This should show up as monospace, preformatted text ⤴</div>",
 			toHtml("```ignored
 (println \"Hello, world!\")
 ```
@@ -34,8 +46,8 @@ This should show up as monospace, preformatted text ⤴")
 
 	public function testPreformattedBlockUnterminated() {
 		Assert.equals(
-			"<blockquote><pre>(println \"Hello, world!\")
-</pre></blockquote><div/><div>The entire blockquote is a preformatted text block, but this line</div><div>is plaintext!</div>",
+			"<blockquote><pre><code class=\"language-ignored\">(println \"Hello, world!\")
+</code></pre></blockquote><div/><div>The entire blockquote is a preformatted text block, but this line</div><div>is plaintext!</div>",
 			toHtml("> ```ignored
 > (println \"Hello, world!\")