Syntax highlighting for MyST Markdown, on top of an editor’s Markdown highlighting.
It has two parts:
myst.tmLanguage.json: a TextMate grammar for editors like VS Code. It injects MyST into the editor’s Markdown grammar.myst-syntax/codemirror: a CodeMirror 6 extension to use withmarkdown().jupyterlab-myst-syntaxadds it to JupyterLab.
Both parts share the regex fragments in myst-syntax/rules, which other tools can import too.
They highlight different syntax:
| Syntax | TextMate | CodeMirror |
|---|---|---|
| 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-syntaxUse 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]