diff --git a/README.md b/README.md index f3000902..c6032708 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Colorization and configuration supports for multiple languages for the Monaco Ed * sql * st * swift +* twig * typescript * vb * xml diff --git a/scripts/bundle.js b/scripts/bundle.js index 251a2439..bf06da5b 100644 --- a/scripts/bundle.js +++ b/scripts/bundle.js @@ -75,6 +75,7 @@ bundleOne('azcli/azcli'); bundleOne('apex/apex'); bundleOne('tcl/tcl'); bundleOne('graphql/graphql'); +bundleOne('twig/twig'); function bundleOne(moduleId, exclude) { requirejs.optimize({ diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index bd1b5303..544ecf2e 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -48,6 +48,7 @@ import './sql/sql.contribution'; import './st/st.contribution'; import './swift/swift.contribution'; import './tcl/tcl.contribution'; +import './twig/twig.contribution'; import './typescript/typescript.contribution'; import './vb/vb.contribution'; import './xml/xml.contribution'; diff --git a/src/twig/twig.contribution.ts b/src/twig/twig.contribution.ts new file mode 100644 index 00000000..d5a02590 --- /dev/null +++ b/src/twig/twig.contribution.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { registerLanguage } from '../_.contribution'; + +registerLanguage({ + id: 'twig', + extensions: ['.twig'], + aliases: ['Twig', 'twig'], + mimetypes: ['text/x-twig'], + loader: () => import('./twig') +}); diff --git a/src/twig/twig.test.ts b/src/twig/twig.test.ts new file mode 100644 index 00000000..327f0e30 --- /dev/null +++ b/src/twig/twig.test.ts @@ -0,0 +1,894 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { testTokenization } from '../test/testRunner'; + +/** + * HTML Tests + */ +testTokenization(['twig', 'css', 'javascript'], [ + + // Open Start Tag #1' + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: '' } + ] + }], + + // Open Start Tag #4 + [{ + line: 'i ', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: 'delimiter.html' } + ] + }], + + // Complete Start Tag with Whitespace + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'delimiter.html' } + ] + }], + + // bug 9809 - Complete Start Tag with Namespaceprefix + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 8, type: 'delimiter.html' } + ] + }], + + // Complete End Tag + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 2, type: 'tag.html' }, + { startIndex: 5, type: 'delimiter.html' } + ] + }], + + // Complete End Tag with Whitespace + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 2, type: 'tag.html' }, + { startIndex: 5, type: '' }, + { startIndex: 7, type: 'delimiter.html' } + ] + }], + + // Empty Tag + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'delimiter.html' } + ] + }], + + // Embedded Content #1 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'attribute.name.html' }, + { startIndex: 12, type: 'delimiter.html' }, + { startIndex: 13, type: 'attribute.value.html' }, + { startIndex: 30, type: 'delimiter.html' }, + { startIndex: 31, type: 'keyword.js' }, + { startIndex: 34, type: '' }, + { startIndex: 35, type: 'identifier.js' }, + { startIndex: 36, type: 'delimiter.js' }, + { startIndex: 37, type: '' }, + { startIndex: 38, type: 'number.js' }, + { startIndex: 40, type: 'delimiter.js' }, + { startIndex: 41, type: 'delimiter.html' }, + { startIndex: 43, type: 'tag.html' }, + { startIndex: 49, type: 'delimiter.html' } + ] + }], + + // Embedded Content #2 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 2, type: 'tag.html' }, + { startIndex: 8, type: 'delimiter.html' } + ] + }], + + // Embedded Content #3 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 2, type: 'tag.html' }, + { startIndex: 8, type: 'delimiter.html' } + ] + }], + + // Embedded Content #4 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'keyword.js' }, + { startIndex: 3, type: '' }, + { startIndex: 4, type: 'identifier.js' }, + { startIndex: 5, type: 'delimiter.js' }, + { startIndex: 6, type: '' }, + { startIndex: 7, type: 'number.js' }, + { startIndex: 9, type: 'delimiter.js' }, + { startIndex: 10, type: 'delimiter.html' }, + { startIndex: 12, type: 'tag.html' }, + { startIndex: 18, type: 'delimiter.html' } + ] + }], + + // Embedded Content #5 + [{ + line: '', + tokens: [ + { startIndex: 0, type: '' }, + { startIndex: 2, type: 'delimiter.html' }, + { startIndex: 4, type: 'tag.html' }, + { startIndex: 10, type: 'delimiter.html' } + ] + }], + + // Embedded Content #6 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 7, type: 'delimiter.html' }, + { startIndex: 8, type: 'identifier.js' }, + { startIndex: 9, type: 'delimiter.html' }, + { startIndex: 11, type: 'tag.html' }, + { startIndex: 17, type: 'delimiter.html' }, + // { startIndex:18, type: 'delimiter.html' }, + { startIndex: 19, type: 'tag.html' }, + { startIndex: 25, type: 'delimiter.html' }, + { startIndex: 26, type: 'identifier.js' }, + { startIndex: 27, type: 'delimiter.html' }, + { startIndex: 29, type: 'tag.html' }, + { startIndex: 35, type: 'delimiter.html' } + ] + }], + + // Embedded Content #7 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'attribute.name.html' }, + { startIndex: 12, type: 'delimiter.html' }, + { startIndex: 13, type: 'attribute.value.html' }, + { startIndex: 30, type: 'delimiter.html' }, + // { startIndex:31, type: 'delimiter.html' }, + { startIndex: 33, type: 'tag.html' }, + { startIndex: 39, type: 'delimiter.html' } + ] + }], + + // Embedded Content #8 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 7, type: 'delimiter.html' }, + { startIndex: 8, type: 'keyword.js' }, + { startIndex: 11, type: '' }, + { startIndex: 12, type: 'identifier.js' }, + { startIndex: 13, type: 'delimiter.js' }, + { startIndex: 14, type: '' }, + { startIndex: 15, type: 'number.js' }, + { startIndex: 17, type: 'delimiter.js' }, + { startIndex: 18, type: 'delimiter.html' }, + { startIndex: 20, type: 'tag.html' }, + { startIndex: 26, type: 'delimiter.html' } + ] + }], + + // Embedded Content #9 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'attribute.name.html' }, + { startIndex: 12, type: 'delimiter.html' }, + { startIndex: 13, type: 'attribute.value.html' }, + { startIndex: 30, type: '' }, + { startIndex: 31, type: 'attribute.name.html' }, + { startIndex: 34, type: 'delimiter.html' }, + { startIndex: 35, type: 'attribute.value.html' }, + { startIndex: 44, type: 'delimiter.html' }, + // { startIndex:45, type: 'delimiter.html' }, + { startIndex: 47, type: 'tag.html' }, + { startIndex: 53, type: 'delimiter.html' } + ] + }], + + // Tag with Attribute + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'attribute.name.html' }, + { startIndex: 8, type: 'delimiter.html' }, + { startIndex: 9, type: 'attribute.value.html' }, + { startIndex: 14, type: 'delimiter.html' } + ] + }], + + // Tag with Empty Attribute Value + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'attribute.name.html' }, + { startIndex: 8, type: 'delimiter.html' }, + { startIndex: 9, type: 'attribute.value.html' }, + { startIndex: 14, type: 'delimiter.html' } + ] + }], + + // Tag with empty attributes + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'attribute.name.html' }, + { startIndex: 8, type: 'delimiter.html' }, + { startIndex: 9, type: 'attribute.value.html' }, + { startIndex: 11, type: 'delimiter.html' } + ] + }], + + // Tag with Attributes + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'attribute.name.html' }, + { startIndex: 8, type: 'delimiter.html' }, + { startIndex: 9, type: 'attribute.value.html' }, + { startIndex: 14, type: '' }, + { startIndex: 15, type: 'attribute.name.html' }, + { startIndex: 18, type: 'delimiter.html' }, + { startIndex: 19, type: 'attribute.value.html' }, + { startIndex: 24, type: 'delimiter.html' } + ] + }], + + // Tag with Attributes, no quotes + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'attribute.name.html' }, + { startIndex: 8, type: 'delimiter.html' }, + { startIndex: 9, type: 'attribute.name.html' }, // slightly incorrect + { startIndex: 12, type: '' }, + { startIndex: 13, type: 'attribute.name.html' }, + { startIndex: 16, type: 'delimiter.html' }, + { startIndex: 17, type: 'attribute.name.html' }, // slightly incorrect + { startIndex: 24, type: 'delimiter.html' } + ] + }], + + // Tag with Attribute And Whitespace + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'attribute.name.html' }, + { startIndex: 8, type: 'delimiter.html' }, + { startIndex: 9, type: '' }, + { startIndex: 11, type: 'attribute.value.html' }, + { startIndex: 16, type: 'delimiter.html' } + ] + }], + + // Tag with Attribute And Whitespace #2 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'attribute.name.html' }, + { startIndex: 8, type: '' }, + { startIndex: 9, type: 'delimiter.html' }, + { startIndex: 10, type: '' }, + { startIndex: 11, type: 'attribute.value.html' }, + { startIndex: 16, type: 'delimiter.html' } + ] + }], + + // Tag with Name-Only-Attribute #1 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'attribute.name.html' }, + { startIndex: 8, type: 'delimiter.html' } + ] + }], + + // Tag with Name-Only-Attribute #2 + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'attribute.name.html' }, + { startIndex: 8, type: '' }, + { startIndex: 9, type: 'attribute.name.html' }, + { startIndex: 12, type: 'delimiter.html' } + ] + }], + + // Tag with Interesting Attribute Name + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'attribute.name.html' }, + { startIndex: 8, type: '' }, + { startIndex: 11, type: 'delimiter.html' }, + { startIndex: 12, type: 'attribute.value.html' }, + { startIndex: 17, type: 'delimiter.html' } + ] + }], + + // Tag with Angular Attribute Name + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 4, type: '' }, + { startIndex: 6, type: 'attribute.name.html' }, + { startIndex: 13, type: '' }, + { startIndex: 15, type: 'attribute.name.html' }, + { startIndex: 20, type: '' }, + { startIndex: 21, type: 'delimiter.html' }, + { startIndex: 22, type: 'attribute.value.html' }, + { startIndex: 27, type: '' }, + { startIndex: 29, type: 'attribute.name.html' }, + { startIndex: 34, type: '' }, + { startIndex: 35, type: 'delimiter.html' }, + { startIndex: 36, type: 'attribute.value.html' }, + { startIndex: 50, type: '' }, + { startIndex: 52, type: 'attribute.name.html' }, + { startIndex: 56, type: 'delimiter.html' }, + { startIndex: 57, type: 'attribute.value.html' }, + { startIndex: 72, type: 'delimiter.html' } + ] + }], + + // Tag with Invalid Attribute Value + [{ + line: '', + tokens: [ + { startIndex: 0, type: 'metatag.content.html' }, + { startIndex: 11, type: 'metatag.html' } + ] + }], + + // PR #14 + [{ + line: 'asd', + tokens: [ + { startIndex: 0, type: 'delimiter.html' }, + { startIndex: 1, type: 'tag.html' }, + { startIndex: 9, type: 'delimiter.html' }, + { startIndex: 10, type: '' }, + { startIndex: 13, type: 'delimiter.html' }, + { startIndex: 15, type: 'tag.html' }, + { startIndex: 23, type: 'delimiter.html' } + ] + }] +]); + +/** + * Twig Tests + */ +testTokenization(['twig'], [ + /** + * Comments + */ + [{ + line: '{# Hello World! #}', + tokens: [ + { startIndex: 0, type: 'comment.twig' }, + ], + }], + [{ + line: '{#Hello World!#}', + tokens: [ + { startIndex: 0, type: 'comment.twig' }, + ], + }], + + /** + * Variables Tags + */ + // Whitespace + [{ + line: '{{}}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{{ }}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'delimiter.twig' }, + ], + }], + // Numbers + [{ + line: '{{1}}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: 'number.twig' }, + { startIndex: 3, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{{ 1 }}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'number.twig' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{{ 1 }}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'number.twig' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{{ 1.1 }}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'number.twig' }, + { startIndex: 6, type: '' }, + { startIndex: 7, type: 'delimiter.twig' }, + ], + }], + // Strings + [{ + line: "{{ 'hi' }}", + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'string.twig' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{{ "hi" }}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'string.twig' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{{ "hi #{1}" }}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'string.twig' }, + { startIndex: 9, type: 'number.twig' }, + { startIndex: 10, type: 'string.twig' }, + { startIndex: 12, type: '' }, + { startIndex: 13, type: 'delimiter.twig' }, + ], + }], + // Variables and functions + [{ + line: '{{ foo }}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'variable.twig' }, + { startIndex: 6, type: '' }, + { startIndex: 7, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{{ foo(42) }}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'variable.twig' }, + { startIndex: 6, type: 'delimiter.twig' }, + { startIndex: 7, type: 'number.twig' }, + { startIndex: 9, type: 'delimiter.twig' }, + { startIndex: 10, type: '' }, + { startIndex: 11, type: 'delimiter.twig' }, + ], + }], + // Operators + [{ + line: '{{ 1 + 2 - 3 / 4 // 5 % 6 * 7 ** 8 }}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'number.twig' }, + { startIndex: 4, type: '' }, + { startIndex: 5, type: 'operators.twig' }, + { startIndex: 6, type: '' }, + { startIndex: 7, type: 'number.twig' }, + { startIndex: 8, type: '' }, + { startIndex: 9, type: 'operators.twig' }, + { startIndex: 10, type: '' }, + { startIndex: 11, type: 'number.twig' }, + { startIndex: 12, type: '' }, + { startIndex: 13, type: 'operators.twig' }, + { startIndex: 14, type: '' }, + { startIndex: 15, type: 'number.twig' }, + { startIndex: 16, type: '' }, + { startIndex: 17, type: 'operators.twig' }, + { startIndex: 19, type: '' }, + { startIndex: 20, type: 'number.twig' }, + { startIndex: 21, type: '' }, + { startIndex: 22, type: 'operators.twig' }, + { startIndex: 23, type: '' }, + { startIndex: 24, type: 'number.twig' }, + { startIndex: 25, type: '' }, + { startIndex: 26, type: 'operators.twig' }, + { startIndex: 27, type: '' }, + { startIndex: 28, type: 'number.twig' }, + { startIndex: 29, type: '' }, + { startIndex: 30, type: 'operators.twig' }, + { startIndex: 32, type: '' }, + { startIndex: 33, type: 'number.twig' }, + { startIndex: 34, type: '' }, + { startIndex: 35, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{{ true and false or true and not false }}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'keyword.twig' }, + { startIndex: 7, type: '' }, + { startIndex: 8, type: 'operators.twig' }, + { startIndex: 11, type: '' }, + { startIndex: 12, type: 'keyword.twig' }, + { startIndex: 17, type: '' }, + { startIndex: 18, type: 'operators.twig' }, + { startIndex: 20, type: '' }, + { startIndex: 21, type: 'keyword.twig' }, + { startIndex: 25, type: '' }, + { startIndex: 26, type: 'operators.twig' }, + { startIndex: 29, type: '' }, + { startIndex: 30, type: 'operators.twig' }, + { startIndex: 33, type: '' }, + { startIndex: 34, type: 'keyword.twig' }, + { startIndex: 39, type: '' }, + { startIndex: 40, type: 'delimiter.twig' }, + ], + }], + + /** + * Block Tags + */ + [{ + line: '{%%}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{% %}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{% for item in navigation %}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'keyword.twig' }, + { startIndex: 6, type: '' }, + { startIndex: 7, type: 'variable.twig' }, + { startIndex: 11, type: '' }, + { startIndex: 12, type: 'operators.twig' }, + { startIndex: 14, type: '' }, + { startIndex: 15, type: 'variable.twig' }, + { startIndex: 25, type: '' }, + { startIndex: 26, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{% verbatim %}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'keyword.twig' }, + { startIndex: 11, type: '' }, + { startIndex: 12, type: 'delimiter.twig' }, + ], + }], + [{ + line: '{% verbatim %}raw data{% endverbatim %}', + tokens: [ + { startIndex: 0, type: 'delimiter.twig' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'keyword.twig' }, + { startIndex: 11, type: '' }, + { startIndex: 12, type: 'delimiter.twig' }, + { startIndex: 14, type: 'string.twig' }, + { startIndex: 22, type: 'delimiter.twig' }, + { startIndex: 24, type: '' }, + { startIndex: 25, type: 'keyword.twig' }, + { startIndex: 36, type: '' }, + { startIndex: 37, type: 'delimiter.twig' }, + ], + }], +]); diff --git a/src/twig/twig.ts b/src/twig/twig.ts new file mode 100644 index 00000000..de4f319b --- /dev/null +++ b/src/twig/twig.ts @@ -0,0 +1,314 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import IRichLanguageConfiguration = monaco.languages.LanguageConfiguration; +import ILanguage = monaco.languages.IMonarchLanguage; + +export const conf: IRichLanguageConfiguration = { + wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g, + + comments: { + blockComment: ['{#', '#}'], + }, + + brackets: [ + ['{#', '#}'], + ['{%', '%}'], + ['{{', '}}'], + ['(', ')'], + ['[', ']'], + + // HTML + [''], + ['<', '>'], + ], + + autoClosingPairs: [ + { open: '{# ', close: ' #}' }, + { open: '{% ', close: ' %}' }, + { open: '{{ ', close: ' }}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: '\'', close: '\'' }, + ], + + surroundingPairs: [ + { open: '"', close: '"' }, + { open: '\'', close: '\'' }, + + // HTML + { open: '<', close: '>' }, + ], +} + +export const language = { + defaultToken: '', + tokenPostfix: '', + ignoreCase: true, + + keywords: [ + // (opening) tags + 'apply', 'autoescape', 'block', 'deprecated', 'do', 'embed', 'extends', + 'flush', 'for', 'from', 'if', 'import', 'include', 'macro', 'sandbox', + 'set', 'use', 'verbatim', 'with', + // closing tags + 'endapply', 'endautoescape', 'endblock', 'endembed', 'endfor', 'endif', + 'endmacro', 'endsandbox', 'endset', 'endwith', + // literals + 'true', 'false', + ], + + tokenizer: { + root: [ + // whitespace + [/\s+/], + + // Twig Tag Delimiters + [/{#/, 'comment.twig', '@commentState'], + [/{%[-~]?/, 'delimiter.twig', '@blockState'], + [/{{[-~]?/, 'delimiter.twig', '@variableState'], + + // HTML + [/)/, ['delimiter.html', 'tag.html', '', 'delimiter.html']], + [/(<)(script)/, ['delimiter.html', { token: 'tag.html', next: '@script' }]], + [/(<)(style)/, ['delimiter.html', { token: 'tag.html', next: '@style' }]], + [/(<)((?:[\w\-]+:)?[\w\-]+)/, ['delimiter.html', { token: 'tag.html', next: '@otherTag' }]], + [/(<\/)((?:[\w\-]+:)?[\w\-]+)/, ['delimiter.html', { token: 'tag.html', next: '@otherTag' }]], + [/|>=|<=/, 'operators.twig'], + // operators - comparison (words) + [/(starts with|ends with|matches)(\s+)/, ['operators.twig', '']], + // operators - containment + [/(in)(\s+)/, ['operators.twig', '']], + // operators - test + [/(is)(\s+)/, ['operators.twig', '']], + // operators - misc + [/\||~|:|\.{1,2}|\?{1,2}/, 'operators.twig'], + // names + [/[^\W\d][\w]*/, { + cases: { + '@keywords': 'keyword.twig', + '@default': 'variable.twig' + } + }], + // numbers + [/\d+(\.\d+)?/, 'number.twig'], + // punctuation + [/\(|\)|\[|\]|{|}|,/, 'delimiter.twig'], + // strings + [/"([^#"\\]*(?:\\.[^#"\\]*)*)"|\'([^\'\\]*(?:\\.[^\'\\]*)*)\'/, 'string.twig'], + // opening double quoted string + [/"/, 'string.twig', '@stringState'], + + // misc syntactic constructs + // These are not operators per se, but for the purposes of lexical analysis we + // can treat them as such. + // arrow functions + [/=>/, 'operators.twig'], + // assignment + [/=/, 'operators.twig'], + ], + + /** + * HTML + */ + doctype: [ + [/[^>]+/, 'metatag.content.html'], + [/>/, 'metatag.html', '@pop'], + ], + + comment: [ + [/-->/, 'comment.html', '@pop'], + [/[^-]+/, 'comment.content.html'], + [/./, 'comment.content.html'] + ], + + otherTag: [ + [/\/?>/, 'delimiter.html', '@pop'], + [/"([^"]*)"/, 'attribute.value.html'], + [/'([^']*)'/, 'attribute.value.html'], + [/[\w\-]+/, 'attribute.name.html'], + [/=/, 'delimiter.html'], + [/[ \t\r\n]+/], // whitespace + ], + + // -- BEGIN