diff --git a/src/basic-languages/mdx/mdx.contribution.ts b/src/basic-languages/mdx/mdx.contribution.ts new file mode 100644 index 00000000..c435bfcf --- /dev/null +++ b/src/basic-languages/mdx/mdx.contribution.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerLanguage } from '../_.contribution'; + +declare var AMD: any; +declare var require: any; + +registerLanguage({ + id: 'mdx', + extensions: ['.mdx'], + aliases: ['MDX', 'mdx'], + loader: () => { + if (AMD) { + return new Promise((resolve, reject) => { + require(['vs/basic-languages/mdx/mdx'], resolve, reject); + }); + } else { + return import('./mdx'); + } + } +}); diff --git a/src/basic-languages/mdx/mdx.test.ts b/src/basic-languages/mdx/mdx.test.ts new file mode 100644 index 00000000..99ef43cc --- /dev/null +++ b/src/basic-languages/mdx/mdx.test.ts @@ -0,0 +1,171 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { testTokenization } from '../test/testRunner'; + +testTokenization( + ['mdx', 'yaml'], + [ + // headers + [ + { + line: '# header 1', + tokens: [{ startIndex: 0, type: 'keyword.mdx' }] + }, + { + line: '## header 2', + tokens: [{ startIndex: 0, type: 'keyword.mdx' }] + }, + { + line: '### header 3', + tokens: [{ startIndex: 0, type: 'keyword.mdx' }] + }, + { + line: '#### header 4', + tokens: [{ startIndex: 0, type: 'keyword.mdx' }] + }, + { + line: '##### header 5', + tokens: [{ startIndex: 0, type: 'keyword.mdx' }] + }, + { + line: '###### header 6', + tokens: [{ startIndex: 0, type: 'keyword.mdx' }] + } + ], + + // Lists + [ + { + line: '- apple', + tokens: [ + { startIndex: 0, type: 'keyword.mdx' }, + { startIndex: 1, type: 'white.mdx' }, + { startIndex: 2, type: '' } + ] + }, + { + line: '* pear', + tokens: [ + { startIndex: 0, type: 'keyword.mdx' }, + { startIndex: 1, type: 'white.mdx' }, + { startIndex: 2, type: '' } + ] + }, + { + line: '+ pineapple', + tokens: [ + { startIndex: 0, type: 'keyword.mdx' }, + { startIndex: 1, type: 'white.mdx' }, + { startIndex: 2, type: '' } + ] + }, + { + line: '1. orange', + tokens: [ + { startIndex: 0, type: 'number.mdx' }, + { startIndex: 2, type: 'white.mdx' }, + { startIndex: 3, type: '' } + ] + } + ], + + // Frontmatter + [ + { + line: '---', + tokens: [{ startIndex: 0, type: 'meta.content.mdx' }] + }, + { + line: 'frontmatter: yaml', + tokens: [ + { startIndex: 0, type: 'type.yaml' }, + { startIndex: 11, type: 'operators.yaml' }, + { startIndex: 12, type: 'white.yaml' }, + { startIndex: 13, type: 'string.yaml' } + ] + }, + { + line: '---', + tokens: [{ startIndex: 0, type: 'meta.content.mdx' }] + } + ], + + // links + [ + { + line: '[MDX](https://mdxjs.com)', + tokens: [ + { startIndex: 0, type: '' }, + { startIndex: 1, type: 'type.identifier.mdx' }, + { startIndex: 4, type: '' }, + { startIndex: 6, type: 'string.link.mdx' }, + { startIndex: 23, type: '' } + ] + }, + { + line: '[monaco][monaco]', + tokens: [ + { startIndex: 0, type: '' }, + { startIndex: 1, type: 'type.identifier.mdx' }, + { startIndex: 7, type: '' }, + { startIndex: 9, type: 'type.identifier.mdx' }, + { startIndex: 15, type: '' } + ] + }, + { + line: '[monaco][]', + tokens: [ + { startIndex: 0, type: '' }, + { startIndex: 1, type: 'type.identifier.mdx' }, + { startIndex: 9, type: '' } + ] + }, + { + line: '[monaco]', + tokens: [ + { startIndex: 0, type: '' }, + { startIndex: 1, type: 'type.identifier.mdx' }, + { startIndex: 7, type: '' } + ] + }, + { + line: '[monaco]: https://github.com/microsoft/monaco-editor', + tokens: [ + { startIndex: 0, type: '' }, + { startIndex: 1, type: 'type.identifier.mdx' }, + { startIndex: 7, type: '' }, + { startIndex: 10, type: 'string.link.mdx' } + ] + } + ], + + // JSX + [ + { + line: '
**child**
', + tokens: [ + { startIndex: 0, type: 'type.identifier.mdx' }, + // This is incorrect. MDX children that start on the same line are JSX, not markdown + { startIndex: 5, type: 'strong.mdx' }, + { startIndex: 14, type: 'type.identifier.mdx' } + ] + }, + { + line: '{console.log("This is JavaScript")}', + tokens: [ + { startIndex: 0, type: 'delimiter.bracket.mdx' }, + { startIndex: 1, type: 'identifier.js' }, + { startIndex: 8, type: 'delimiter.js' }, + { startIndex: 9, type: 'identifier.js' }, + { startIndex: 12, type: 'delimiter.parenthesis.js' }, + { startIndex: 13, type: 'string.js' }, + { startIndex: 33, type: 'delimiter.parenthesis.js' }, + { startIndex: 34, type: 'delimiter.bracket.mdx' } + ] + } + ] + ] +); diff --git a/src/basic-languages/mdx/mdx.ts b/src/basic-languages/mdx/mdx.ts new file mode 100644 index 00000000..dfbc01e8 --- /dev/null +++ b/src/basic-languages/mdx/mdx.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { languages } from '../../fillers/monaco-editor-core'; + +export const conf: languages.LanguageConfiguration = { + comments: { + blockComment: ['{/*', '*/}'] + }, + brackets: [['{', '}']], + autoClosingPairs: [ + { open: '"', close: '"' }, + { open: "'", close: "'" }, + { open: '“', close: '”' }, + { open: '‘', close: '’' }, + { open: '`', close: '`' }, + { open: '{', close: '}' }, + { open: '(', close: ')' }, + { open: '_', close: '_' }, + { open: '**', close: '**' }, + { open: '<', close: '>' } + ], + onEnterRules: [ + { + beforeText: /^\s*- .+/, + action: { indentAction: languages.IndentAction.None, appendText: '- ' } + }, + { + beforeText: /^\s*\+ .+/, + action: { indentAction: languages.IndentAction.None, appendText: '+ ' } + }, + { + beforeText: /^\s*\* .+/, + action: { indentAction: languages.IndentAction.None, appendText: '* ' } + }, + { + beforeText: /^> /, + action: { indentAction: languages.IndentAction.None, appendText: '> ' } + }, + { + beforeText: /<\w+/, + action: { indentAction: languages.IndentAction.Indent } + }, + { + beforeText: /\s+>\s*$/, + action: { indentAction: languages.IndentAction.Indent } + }, + { + beforeText: /<\/\w+>/, + action: { indentAction: languages.IndentAction.Outdent } + }, + ...Array.from({ length: 100 }, (_, index) => ({ + beforeText: new RegExp(`^${index}\\. .+`), + action: { indentAction: languages.IndentAction.None, appendText: `${index + 1}. ` } + })) + ] +}; + +export const language = { + defaultToken: '', + tokenPostfix: '.mdx', + control: /[!#()*+.[\\\]_`{}\-]/, + escapes: /\\@control/, + + tokenizer: { + root: [ + [/^---$/, { token: 'meta.content', next: '@frontmatter', nextEmbedded: 'yaml' }], + [/^\s*import/, { token: 'keyword', next: '@import', nextEmbedded: 'js' }], + [/^\s*export/, { token: 'keyword', next: '@export', nextEmbedded: 'js' }], + [/<\w+/, { token: 'type.identifier', next: '@jsx' }], + [/<\/?\w+>/, 'type.identifier'], + [ + /^(\s*)(>*\s*)(#{1,6}\s)/, + [{ token: 'white' }, { token: 'comment' }, { token: 'keyword', next: '@header' }] + ], + [/^(\s*)(>*\s*)([*+-])(\s+)/, ['white', 'comment', 'keyword', 'white']], + [/^(\s*)(>*\s*)(\d{1,9}\.)(\s+)/, ['white', 'comment', 'number', 'white']], + [/^(\s*)(>*\s*)(\d{1,9}\.)(\s+)/, ['white', 'comment', 'number', 'white']], + [/^(\s*)(>*\s*)(-{3,}|\*{3,}|_{3,})$/, ['white', 'comment', 'keyword']], + [/`{3,}(\s.*)?$/, { token: 'string', next: '@codeblock_backtick' }], + [/~{3,}(\s.*)?$/, { token: 'string', next: '@codeblock_tilde' }], + [ + /`{3,}(\S+).*$/, + { token: 'string', next: '@codeblock_highlight_backtick', nextEmbedded: '$1' } + ], + [ + /~{3,}(\S+).*$/, + { token: 'string', next: '@codeblock_highlight_tilde', nextEmbedded: '$1' } + ], + [/^(\s*)(-{4,})$/, ['white', 'comment']], + [/^(\s*)(>+)/, ['white', 'comment']], + { include: 'content' } + ], + content: [ + [ + /(\[)(.+)(]\()(.+)(\s+".*")(\))/, + ['', 'string.link', '', 'type.identifier', 'string.link', ''] + ], + [/(\[)(.+)(]\()(.+)(\))/, ['', 'type.identifier', '', 'string.link', '']], + [/(\[)(.+)(]\[)(.+)(])/, ['', 'type.identifier', '', 'type.identifier', '']], + [/(\[)(.+)(]:\s+)(\S*)/, ['', 'type.identifier', '', 'string.link']], + [/(\[)(.+)(])/, ['', 'type.identifier', '']], + [/`.*`/, 'variable.source'], + [/_/, { token: 'emphasis', next: '@emphasis_underscore' }], + [/\*(?!\*)/, { token: 'emphasis', next: '@emphasis_asterisk' }], + [/\*\*/, { token: 'strong', next: '@strong' }], + [/{/, { token: 'delimiter.bracket', next: '@expression', nextEmbedded: 'js' }] + ], + import: [[/'\s*(;|$)/, { token: 'string', next: '@pop', nextEmbedded: '@pop' }]], + expression: [ + [/{/, { token: 'delimiter.bracket', next: '@expression' }], + [/}/, { token: 'delimiter.bracket', next: '@pop', nextEmbedded: '@pop' }] + ], + export: [[/^\s*$/, { token: 'delimiter.bracket', next: '@pop', nextEmbedded: '@pop' }]], + jsx: [ + [/\s+/, ''], + [/(\w+)(=)("(?:[^"\\]|\\.)*")/, ['attribute.name', 'operator', 'string']], + [/(\w+)(=)('(?:[^'\\]|\\.)*')/, ['attribute.name', 'operator', 'string']], + [/(\w+(?=\s|>|={|$))/, ['attribute.name']], + [/={/, { token: 'delimiter.bracket', next: '@expression', nextEmbedded: 'js' }], + [/>/, { token: 'type.identifier', next: '@pop' }] + ], + header: [ + [/.$/, { token: 'keyword', next: '@pop' }], + { include: 'content' }, + [/./, { token: 'keyword' }] + ], + strong: [ + [/\*\*/, { token: 'strong', next: '@pop' }], + { include: 'content' }, + [/./, { token: 'strong' }] + ], + emphasis_underscore: [ + [/_/, { token: 'emphasis', next: '@pop' }], + { include: 'content' }, + [/./, { token: 'emphasis' }] + ], + emphasis_asterisk: [ + [/\*(?!\*)/, { token: 'emphasis', next: '@pop' }], + { include: 'content' }, + [/./, { token: 'emphasis' }] + ], + frontmatter: [[/^---$/, { token: 'meta.content', nextEmbedded: '@pop', next: '@pop' }]], + codeblock_highlight_backtick: [ + [/\s*`{3,}\s*$/, { token: 'string', next: '@pop', nextEmbedded: '@pop' }], + [/.*$/, 'variable.source'] + ], + codeblock_highlight_tilde: [ + [/\s*~{3,}\s*$/, { token: 'string', next: '@pop', nextEmbedded: '@pop' }], + [/.*$/, 'variable.source'] + ], + codeblock_backtick: [ + [/\s*`{3,}\s*$/, { token: 'string', next: '@pop' }], + [/.*$/, 'variable.source'] + ], + codeblock_tilde: [ + [/\s*~{3,}\s*$/, { token: 'string', next: '@pop' }], + [/.*$/, 'variable.source'] + ] + } +}; diff --git a/src/basic-languages/monaco.contribution.ts b/src/basic-languages/monaco.contribution.ts index 74531ece..b6c07a54 100644 --- a/src/basic-languages/monaco.contribution.ts +++ b/src/basic-languages/monaco.contribution.ts @@ -39,6 +39,7 @@ import './lua/lua.contribution'; import './liquid/liquid.contribution'; import './m3/m3.contribution'; import './markdown/markdown.contribution'; +import './mdx/mdx.contribution'; import './mips/mips.contribution'; import './msdax/msdax.contribution'; import './mysql/mysql.contribution'; diff --git a/website/index/samples/sample.mdx.txt b/website/index/samples/sample.mdx.txt new file mode 100644 index 00000000..295a0c28 --- /dev/null +++ b/website/index/samples/sample.mdx.txt @@ -0,0 +1,92 @@ +--- +frontmatter: data +yaml: true +--- + +[link](https://example.com) + +~~~ +aasd +asd +asd +~~~ + +# Hello MDX {1+2} + +import { MyComponent } from './MyComponent' + +This is **bold {'foo' + 1} text** + +This is _emphasis {'foo' + 1} text_ + +This is *emphasis {'foo' + 1} text too* + + This is an indented *code* block + +export function foo() { + console.log('asd', 1) + if(true) { + return 'yep' + } + return 'nope' +} + + +This is regular content + +- this is a list + +* this is also a list + ++ me too! + +1. pizza +2. fries +3. ice cream + +---- + +_________ + +***\ +~~~css +body { + color: red; +} +~~~ + +> - this is a list +> +> * this is also a list +> +> + me too! +> +> 1. pizza +> 2. fries +> 3. ice cream +> +> --- +> +> _________ +> +> *** +> +> ```css +> body { +> color: red; +> } +> ``` + +> This is a blockquote +> +>> This is a nested {'blockquote'} + +{'foo' + 1 + 2 + {} + 12} + +{/* this is a comment */} + + + + This is **also** markdown. + + diff --git a/website/src/website/data/home-samples/sample.mdx.txt b/website/src/website/data/home-samples/sample.mdx.txt new file mode 100644 index 00000000..0e2479f9 --- /dev/null +++ b/website/src/website/data/home-samples/sample.mdx.txt @@ -0,0 +1,91 @@ +--- +title: Hello! +--- + +import {Chart} from './chart.js' +import population from './population.js' +import {External} from './some/place.js' + +export const year = 2018 +export const pi = 3.14 + +export function SomeComponent(props) { + const name = (props || {}).name || 'world' + + return
+

Hi, {name}!

+ +

and some more things

+
+} + +export function Local(props) { + return +} + +# Last year’s snowfall + +In {year}, the snowfall was above average. +It was followed by a warm spring which caused +flood conditions in many of the nearby rivers. + + + +
+ > Some notable things in a block quote! +
+ +# Heading (rank 1) +## Heading 2 +### 3 +#### 4 +##### 5 +###### 6 + +> Block quote + +* Unordered +* List + +1. Ordered +2. List + +A paragraph, introducing a thematic break: + +--- + +```js +// Get an element. +const element = document.querySelectorAll('#hi') + +// Add a class. +element.classList.add('asd') +``` + +a [link](https://example.com), an ![image](./image.png), some *emphasis*, +something **strong**, and finally a little `code()`. + +} +/> + +Two 🍰 is: {Math.PI * 2} + +{(function () { + const guess = Math.random() + + if (guess > 0.66) { + return Look at us. + } + + if (guess > 0.33) { + return Who would have guessed?! + } + + return Not me. +})()} + +{/* A comment! */}