From adc251813d7f5deb8aba6aaa852818b5dc92c832 Mon Sep 17 00:00:00 2001 From: max-nothacker Date: Sat, 25 Jul 2026 16:39:38 +0200 Subject: [PATCH] Fix single-line display math with attributes swallowing rest of document The single-line close check in math_block() only recognized lines ending in "$$", so "$$1+1$$ {#eq-spec0}" fell through to the multi-line scan, which never finds a closing line and consumes the remainder of the document into one math token. Everything after the equation (headings, code cells) was no longer tokenized: no outline entries, no LaTeX preview, and "Editor selection is not within an executable cell" when running later cells. Recognize an optional trailing attribute block in the single-line case, mirroring the multi-line close regex, and store it in token.info the same way. Adds unit tests for the single-line labeled, single-line unlabeled, and multi-line labeled cases. Fixes #976 Co-Authored-By: Claude Fable 5 in the session with id 6b8b182e --- packages/core/src/markdownit/math.ts | 10 +++--- packages/core/test/markdownit-math.test.ts | 42 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 packages/core/test/markdownit-math.test.ts diff --git a/packages/core/src/markdownit/math.ts b/packages/core/src/markdownit/math.ts index 4abc7099c..959d0e2bf 100644 --- a/packages/core/src/markdownit/math.ts +++ b/packages/core/src/markdownit/math.ts @@ -163,13 +163,15 @@ function math_block( if (silent) { return true; } - if (firstLine.trim().slice(-2) === "$$") { - // Single line expression - firstLine = firstLine.trim().slice(0, -2); + let attrStr = undefined; + const singleLineMatch = firstLine.trim().match(/^(.*)\$\$\s*(\{.*\})?\s*$/); + if (singleLineMatch) { + // Single line expression, optionally followed by attributes (e.g. {#eq-label}) + firstLine = singleLineMatch[1]; + attrStr = singleLineMatch[2]; found = true; } - let attrStr = undefined; for (next = start; !found; ) { next++; diff --git a/packages/core/test/markdownit-math.test.ts b/packages/core/test/markdownit-math.test.ts new file mode 100644 index 000000000..ba04be920 --- /dev/null +++ b/packages/core/test/markdownit-math.test.ts @@ -0,0 +1,42 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import MarkdownIt from "markdown-it"; +import { mathjaxPlugin } from "../src/markdownit/math"; + +const parse = (src: string) => { + const md = new MarkdownIt({ html: true }); + md.use(mathjaxPlugin); + return md.parse(src, {}); +}; + +test("single-line math with attributes does not swallow the rest of the document", () => { + const tokens = parse( + "$$1+1$$ {#eq-spec0}\n\n```{python}\n2+2\n```\n" + ); + const math = tokens.filter((t) => t.type === "math_block"); + const fences = tokens.filter((t) => t.type === "fence"); + assert.equal(math.length, 1); + assert.equal(math[0].info, "{#eq-spec0}"); + assert.equal(math[0].content.trim(), "1+1"); + assert.equal(fences.length, 1); +}); + +test("single-line math without attributes still parses", () => { + const tokens = parse("$$1+1$$\n\n```{python}\n2+2\n```\n"); + const math = tokens.filter((t) => t.type === "math_block"); + const fences = tokens.filter((t) => t.type === "fence"); + assert.equal(math.length, 1); + assert.equal(math[0].content.trim(), "1+1"); + assert.equal(fences.length, 1); +}); + +test("multi-line math with attributes on the closing line still parses", () => { + const tokens = parse("$$\n1+1\n$$ {#eq-spec0}\n\n```{python}\n2+2\n```\n"); + const math = tokens.filter((t) => t.type === "math_block"); + const fences = tokens.filter((t) => t.type === "fence"); + assert.equal(math.length, 1); + assert.equal(math[0].info, "{#eq-spec0}"); + assert.equal(math[0].content.trim(), "1+1"); + assert.equal(fences.length, 1); +});