Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

myst-syntax

Syntax highlighting for MyST Markdown, on top of an editor’s Markdown highlighting.

It has two parts:

Both parts share the regex fragments in myst-syntax/rules, which other tools can import too.

They highlight different syntax:

SyntaxTextMateCodeMirror
Roles, directives, options and labels✓✓
Comments (%) and block breaks (+++)✓✓
Substitutions ({{ x }}) and footnote references ([^1])✓✓
YAML options in a directive✓No
Inline math ($x$)✓✓
Display math ($$)✓No
Citations (@key, [@a; @b])✓✓
Code in code directives, like {code-cell} python✓✓

What we learned says why CodeMirror skips YAML options and display math.

Both parts handle backtick, tilde and colon fences, at the top level and inside lists and blockquotes. TextMate highlights code directives in common languages like Python, and leaves other code, {math}, {mermaid} and {raw} plain.

The TextMate grammar takes inspiration from chrisjsewell’s vscode-myst-syntax. See the roadmap for what’s next, and what we learned for why it works the way it does. To work on the package itself, see CONTRIBUTING.md.

Install it

Install it from npm:

npm install myst-syntax

Use it

In a VS Code extension, copy node_modules/myst-syntax/myst.tmLanguage.json into the extension when you build it. Then add the copy to the extension’s package.json:

"contributes": {
  "grammars": [
    { "scopeName": "text.myst.injection", "path": "./myst.tmLanguage.json", "injectTo": ["text.html.markdown"] }
  ]
}

The repo’s own VS Code extension does this, copying the grammar from packages/syntax.

In CodeMirror:

import { markdown } from '@codemirror/lang-markdown';
import { languages } from '@codemirror/language-data';
import { EditorView } from '@codemirror/view';
import { myst, mystCode } from 'myst-syntax/codemirror';

new EditorView({ extensions: [markdown({ codeLanguages: languages, extensions: [mystCode(languages)] }), myst()] });

myst() adds a cm-myst-<name> class to each match, such as cm-myst-role, with colours for light and dark themes. It skips code, so @property in a Python block isn’t a citation. mystCode highlights code directives in their language, as codeLanguages does for plain ```python blocks. It also parses colon-fenced code directives, like :::{code-cell} python, as code, which Markdown doesn’t. Leave out both if you don’t want code highlighted.

Using with Shiki

Shiki can load the grammar and inject it into Markdown:

import { createHighlighter } from 'shiki';
import myst from 'myst-syntax/myst.tmLanguage.json' with { type: 'json' };

const highlighter = await createHighlighter({ themes: ['github-light'], langs: ['yaml', 'markdown', myst] });
highlighter.codeToHtml(src, { lang: 'markdown', theme: 'github-light' });

Load yaml first, then markdown, then the grammar, and nothing else. The order works around a Shiki bug, described in what we learned. To load more languages, pass Markdown with embeddedLangsLazy emptied, as below, and then the order doesn’t matter:

import markdown from '@shikijs/langs/markdown';

langs: ['yaml', 'python', { ...markdown[0], embeddedLangsLazy: [] }, myst]