-
-
Save rpaul-stripe/986ab19947a9e53236dffcc55694663e to your computer and use it in GitHub Desktop.
Monaco editor for Markdoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { editor, languages, MarkerSeverity, IRange } from "monaco-editor"; | |
import * as Markdoc from "@markdoc/markdoc"; | |
import language from "./language"; | |
// Markdoc config | |
const config = {}; | |
const model = editor.createModel("", "markdoc"); | |
window.MonacoEnvironment = { | |
getWorkerUrl: () => "./vs/editor/editor.worker.js" | |
}; | |
languages.register({ | |
id: "markdoc", | |
extensions: [".md"], | |
aliases: ["markdown"], | |
mimetypes: ["text/markdown"], | |
}); | |
function getContentRangeInLine(line: number, text: string): IRange { | |
const lineContent = model.getLineContent(line); | |
const startColumn = lineContent.indexOf(text) + 1; | |
const endColumn = startColumn + text.length; | |
const range = { | |
startLineNumber: line, | |
endLineNumber: line, | |
startColumn, | |
endColumn, | |
}; | |
return model.validateRange(range); | |
} | |
function severity(level: string): MarkerSeverity { | |
switch (level) { | |
case "debug": | |
case "info": | |
return MarkerSeverity.Info; | |
case "warning": | |
return MarkerSeverity.Warning; | |
default: | |
return MarkerSeverity.Error; | |
} | |
} | |
function errorMarker(error: Markdoc.ValidateError): editor.IMarkerData { | |
const { | |
lines: [startLine, endLineNumber], | |
error: {id: code, level, message}, | |
} = error; | |
return { | |
code, message, | |
source: 'markdoc', | |
severity: severity(level), | |
startLineNumber: startLine + 1, | |
startColumn: 0, | |
endLineNumber, | |
endColumn: model.getLineMaxColumn(endLineNumber), | |
} | |
} | |
let ast: Markdoc.Node | null = null; | |
languages.setMonarchTokensProvider("markdoc", language); | |
languages.registerFoldingRangeProvider("markdoc", { | |
provideFoldingRanges(model, context, token) { | |
const ranges: languages.FoldingRange[] = []; | |
if (!ast) return ranges; | |
for (const {type, lines} of ast.walk()) | |
if (type.startsWith('tag') && lines.length === 4) | |
ranges.push({start: lines[0] + 1, end: lines[2] + 1}); | |
return ranges; | |
}, | |
}), | |
languages.registerLinkProvider("markdoc", { | |
provideLinks(model, token) { | |
const links: languages.ILink[] = []; | |
if (!ast) return {links}; | |
for (const node of ast.walk()) { | |
if (node.type === 'tag' && node.tag === 'partial') { | |
const {file} = node.attributes; | |
if (typeof file !== 'string' || typeof node.lines[0] !== 'number') { | |
continue; | |
} | |
links.push({ | |
url: ``, // Logic for generating editor links here | |
tooltip: 'Edit partial in new tab', | |
range: getContentRangeInLine(node.lines[1], file), | |
}); | |
} | |
} | |
return {links}; | |
}, | |
}); | |
model.onDidChangeContent(ev => { | |
const text = model.getValue(); | |
ast = Markdoc.parse(text); | |
const errors = Markdoc.validate(ast, config); | |
const markers = errors.map(errorMarker); | |
editor.setModelMarkers(model, "markdoc", markers); | |
}); | |
editor.setTheme("vs-dark"); | |
editor.create(document.getElementById("editor"), {model}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Forked from Monaco's official Markdown highlighting rules | |
import type {languages} from 'monaco-editor'; | |
const language: languages.IMonarchLanguage = { | |
defaultToken: '', | |
tokenPostfix: '.md', | |
// escape codes | |
control: /[\\`*_\[\]{}()#+\-\.!]/, | |
noncontrol: /[^\\`*_\[\]{}()#+\-\.!]/, | |
escapes: /\\(?:@control)/, | |
// escape codes for javascript/CSS strings | |
jsescapes: /\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/, | |
tokenizer: { | |
root: [ | |
// headers (with #) | |
[ | |
/^(\s*)(#+)((?:[^\\#]|@escapes)+)(.*$)/, | |
['white', 'keyword', 'keyword', 'keyword'], | |
], | |
// headers (with =) | |
[/^\s*(=+|\-+)\s*$/, 'keyword'], | |
// headers (with ***) | |
[/^\s*((\*[ ]?)+)\s*$/, 'meta.separator'], | |
// quote | |
[/^\s*>+/, 'comment'], | |
// list (starting with * or number) | |
[/^\s*([\*\-+:]|\d+\.)\s/, 'keyword'], | |
// code block (3 tilde) | |
[ | |
/^\s*~~~\s*((?:\w|[\/\-#])+)?\s*$/, | |
{token: 'string', next: '@codeblock'}, | |
], | |
// github style code blocks (with backticks and language) | |
[ | |
/^\s*```\s*((?:\w|[\/\-#])+)\s*.*$/, | |
{token: 'string', next: '@codeblockgh', nextEmbedded: '$1'}, | |
], | |
// github style code blocks (with backticks but no language) | |
[/^\s*```\s*$/, {token: 'string', next: '@codeblock'}], | |
// markup within lines | |
{include: '@linecontent'}, | |
], | |
codeblock: [ | |
[/^\s*~~~\s*$/, {token: 'string', next: '@pop'}], | |
[/^\s*```\s*$/, {token: 'string', next: '@pop'}], | |
[/.*$/, 'variable.source'], | |
], | |
// github style code blocks | |
codeblockgh: [ | |
[ | |
/```\s*$/, | |
{token: 'variable.source', next: '@pop', nextEmbedded: '@pop'}, | |
], | |
[/[^`]+/, 'variable.source'], | |
], | |
linecontent: [ | |
// escapes | |
[/&\w+;/, 'string.escape'], | |
[/@escapes/, 'escape'], | |
// various markup | |
[/\b__([^\\_]|@escapes|_(?!_))+__\b/, 'strong'], | |
[/\*\*([^\\*]|@escapes|\*(?!\*))+\*\*/, 'strong'], | |
[/\b_[^_]+_\b/, 'emphasis'], | |
[/\*([^\\*]|@escapes)+\*/, 'emphasis'], | |
[/`([^\\`]|@escapes)+`/, 'variable'], | |
// links | |
[ | |
/(!?\[)((?:[^\]\\]|@escapes)*)(\]\([^\)]+\))/, | |
['string.link', '', 'string.link'], | |
], | |
[/(!?\[)((?:[^\]\\]|@escapes)*)(\])/, 'string.link'], | |
// Markdoc tags | |
[ | |
/({%\s*\/?)([a-zA-Z0-9_-]+)/, | |
[{token: 'tag'}, {token: 'type', next: '@tag'}], | |
], | |
[/{%\s*/, {token: 'tag', next: '@tag'}], | |
], | |
tag: [ | |
[/[ \t\r\n]+/, 'white'], | |
[/(\$|@)[a-zA-Z0-9_-]+/, 'variable.name'], | |
[/(\.|#)[a-zA-Z0-9_-]+/, 'attribute.value'], | |
[/(\w+)(\s*=\s*)/, ['attribute.name', 'delimiter']], | |
[/"[^"]+"/, 'string'], | |
[/[0-9]+/, 'number'], | |
[/true|false/, 'keyword'], | |
[/([a-zA-Z0-9_-]+)(\()/, ['identifier', 'delimiter']], | |
[/\/?%}/, 'tag', '@pop'], | |
], | |
}, | |
}; | |
export default language; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment