Skip to content

T-JPB0-md-engine-math

State: closed/done · Impact: high · Complexity: medium

Math is the one v2 construct with no Lezer nodes: @codemirror/lang-markdown parses tables and HTML blocks but not $…$ or $$…$$, so the editor cannot even see math. This task adds the mathSyntax Lezer extension beside obsidianSyntax and the math plugin whose MathWidget renders KaTeX markup from a cache.

LocationRole today
packages/ts/obsidian-editor/src/markdown-extension.ts#obsidianSyntaxWikilinks, embeds, tags, highlight; no math nodes
packages/ts/obsidian-editor/src/decorations.tsNo math handling; $x^2$ renders as plain text in live mode
packages/ts/obsidian-md/src/vue/components/MathBlock.tsRead-mode KaTeX component; T-RCWI-md-obsidian-md-sync-helpers extracts renderMath from it
solutions/determined/experiences/notes/nuxt.config.tsHosts import katex/dist/katex.min.css; unchanged by this task
packages/ts/obsidian-editor/test/helpers.ts#makeStateHeadless state builder for the parse and plugin tests

The plugin roster comes from T-4TE2-md-engine-plugin-contract; the math.md corpus fixture and requestRedraw from T-0GV4-md-engine-line-endings-corpus.

mathSyntax: MarkdownConfig in the markdown-extension module, exported beside obsidianSyntax: node names InlineMath ($…$, no line break inside), BlockMath ($$…$$, spanning lines) and MathMark for the delimiters. It is the math plugin’s syntax contribution — the D-7VMX parser seam — so omit: ['core.math'] removes the dialect with the plugin.

packages/ts/obsidian-editor/src/plugins/math.ts:

  • Inactive InlineMath → inline Decoration.replace widget; inactive BlockMath → block widget. Active line → delimiters and TeX source visible, cm-math-source mark.
  • MathWidget fills from a cache keyed by displayMode + latex. On a miss it shows the raw TeX, calls renderMath(latex, displayMode) (async KaTeX from T-RCWI-md-obsidian-md-sync-helpers, throwOnError: false) and issues requestRedraw(view) when the markup resolves, so invalid TeX still renders KaTeX’s error markup instead of throwing.
  • Hosts keep importing katex/dist/katex.min.css; the plugin ships no CSS beyond --obsmd-* hooks for the widget container.
  1. Write packages/ts/obsidian-editor/test/syntax/math.test.ts first: $x$ parses to InlineMath with two MathMark children; a $$ fence across three lines parses to BlockMath; a $ inside inline code or a fenced block produces no math node; $ x$ with a leading space is not InlineMath.

  2. Write packages/ts/obsidian-editor/test/plugins/math.plugin.test.ts: inactive inline math →

    replace widget; before renderMath resolves the widget shows the TeX source; after resolution plus requestRedraw the widget DOM contains KaTeX markup; the cursor entering the range restores the source; two rebuilds call renderMath once per distinct formula (spy).

  3. Implement mathSyntax in the markdown-extension module.

  4. Implement the plugin: widget class, cache, redraw-on-resolve; add math to

    BUILTIN_PLUGINS.

  5. Export mathSyntax from the package entry; bun test, tsc --noEmit, moon run obsidian-editor:bench.

LocationKindChange
packages/ts/obsidian-editor/src/markdown-extension.tsmodifymathSyntax with InlineMath, BlockMath, MathMark
packages/ts/obsidian-editor/src/plugins/math.tsnewmath plugin: MathWidget, cache, redraw on resolve
packages/ts/obsidian-editor/src/plugins/index.tsnewCreated by T-4TE2-md-engine-plugin-contract; math joins BUILTIN_PLUGINS
packages/ts/obsidian-editor/src/index.tsmodifyExport mathSyntax
packages/ts/obsidian-editor/test/syntax/math.test.tsnewLezer node tests
packages/ts/obsidian-editor/test/plugins/math.plugin.test.tsnewWidget, cache and active-line tests
  • AC-1: bun test packages/ts/obsidian-editor passes, including the two new suites.
  • AC-2: the four parse cases in Approach step 1 hold: inline and block nodes exist with MathMark delimiters, and no math node appears inside inline code or a fence.
  • AC-3: after renderMath resolves and a requestRedraw, the widget DOM contains an element with a katex class; before resolution the widget shows the raw TeX.
  • AC-4: invalid TeX renders (no throw escapes the widget) — asserted with a formula KaTeX rejects under throwOnError: true.
  • AC-5: two decoration rebuilds with an unchanged document call renderMath once per distinct formula.
  • AC-6: bun test packages/ts/obsidian-editor/test/swap-writer.test.ts passes unchanged over packages/ts/obsidian-editor/test/fixtures/corpus/math.md.
  • AC-7: moon run obsidian-editor:bench stays within 1.5× of the recorded baseline.

Member of milestone M-931L; engine step E7 of decision D-K3EM-markdown-editor-chrome-and-plugin-environment.

T-RCWI-md-obsidian-md-sync-helpers


← Back to Tasks