Add support for rendering math in Markdown with MathJax

GitOrigin-RevId: dd1c1c72237a5364c642e46c66e08ea7a6977662
This commit is contained in:
Domagoj Kriskovic
2026-06-22 08:05:40 +00:00
committed by Copybot
parent ef05288f81
commit 63a605484c
2 changed files with 59 additions and 1 deletions
@@ -2,6 +2,7 @@ import { markdown as markdownLanguage } from '@codemirror/lang-markdown'
import { shortcuts } from './shortcuts'
import { languages } from '../index'
import { Strikethrough, Table } from '@lezer/markdown'
import { Math } from './math'
import {
HighlightStyle,
LanguageSupport,
@@ -12,7 +13,7 @@ import { tags } from '@lezer/highlight'
export const markdown = () => {
const { language, support } = markdownLanguage({
codeLanguages: languages,
extensions: [Strikethrough, Table],
extensions: [Strikethrough, Table, Math],
})
return new LanguageSupport(language, [
@@ -0,0 +1,57 @@
import { InlineContext, MarkdownConfig, InlineParser } from '@lezer/markdown'
const DOLLAR = 36 /* '$' */
// Whitespace (including the start/end of the inline section, where `char`
// returns -1).
const isSpace = (code: number) =>
code === -1 || code === 32 || code === 9 || code === 10 || code === 13
const isDigit = (code: number) => code >= 48 && code <= 57
const InlineMathDelim = { resolve: 'InlineMath', mark: 'InlineMathMark' }
const BlockMathDelim = { resolve: 'BlockMath', mark: 'BlockMathMark' }
// Inline parsing runs over the whole paragraph, so `$$…$$` handles multi-line
// equations as long as there is no blank line inside them.
const blockMathParser: InlineParser = {
name: 'BlockMath',
parse(cx: InlineContext, next: number, pos: number) {
if (next !== DOLLAR || cx.char(pos + 1) !== DOLLAR) {
return -1
}
return cx.addDelimiter(BlockMathDelim, pos, pos + 2, true, true)
},
// Run before the default `Emphasis` parser, and (via `InlineMath`'s `after`)
// before the single-dollar inline parser, so `$$` is matched as a display
// delimiter rather than two empty inline ones.
before: 'Emphasis',
}
const inlineMathParser: InlineParser = {
name: 'InlineMath',
parse(cx: InlineContext, next: number, pos: number) {
if (next !== DOLLAR) {
return -1
}
const before = cx.char(pos - 1)
const after = cx.char(pos + 1)
const canOpen = !isSpace(after)
const canClose = !isSpace(before) && !isDigit(after)
if (!canOpen && !canClose) {
return -1
}
return cx.addDelimiter(InlineMathDelim, pos, pos + 1, canOpen, canClose)
},
// Run after `BlockMath` so `$$` is tried as a display delimiter first, but
// still before `Emphasis`. The default `Escape` parser consumes `\$` first.
after: 'BlockMath',
}
export const Math: MarkdownConfig = {
// No `style` is set on these nodes: math is rendered by the visual editor
// (which slices the raw source), so the grammar change stays inert for
// source-mode syntax highlighting and non-experiment users.
defineNodes: ['InlineMath', 'InlineMathMark', 'BlockMath', 'BlockMathMark'],
parseInline: [blockMathParser, inlineMathParser],
}