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:

They don’t highlight quite the same 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 plain. It also leaves {math}, {mermaid} and {raw} plain in backtick and tilde fences. In colon fences, TextMate still highlights the MyST inside these directives.

The TextMate grammar takes inspiration from chrisjsewell’s vscode-myst-syntax. See the issues 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. It comes with colours for light and dark themes. It skips code, so @property in a Python block isn’t highlighted as 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 codeLanguages and mystCode 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. Then you can load them in any order:

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

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