From d0f7359bc0d71c53b7c63be69acf595624509cd3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sat, 17 Sep 2016 01:16:53 +0200 Subject: [PATCH] Add handlebars --- README.md | 1 + gulpfile.js | 1 + package.json | 2 +- src/handlebars.ts | 236 ++++++++++++++++++++++++++++++ src/monaco.contribution.ts | 7 + test/all.js | 1 + test/handlebars.test.ts | 290 +++++++++++++++++++++++++++++++++++++ tsconfig.json | 2 + 8 files changed, 539 insertions(+), 1 deletion(-) create mode 100644 src/handlebars.ts create mode 100644 test/handlebars.test.ts diff --git a/README.md b/README.md index 80c22719..7cf3072c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Colorization and configuration supports for multiple languages for the Monaco Ed * csharp * fsharp * go +* handlebars * html * ini * jade diff --git a/gulpfile.js b/gulpfile.js index 753b599f..2d0f8869 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -53,6 +53,7 @@ gulp.task('release', ['clean-release','compile'], function() { bundleOne('src/dockerfile'), bundleOne('src/fsharp'), bundleOne('src/go'), + bundleOne('src/handlebars'), bundleOne('src/html'), bundleOne('src/ini'), bundleOne('src/jade'), diff --git a/package.json b/package.json index d77e897d..a7f41cb4 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "jsdom-no-contextify": "^3.1.0", "merge-stream": "^1.0.0", "mocha": "^2.5.3", - "monaco-editor-core": "0.7.0-next.2", + "monaco-editor-core": "0.7.0-next.3", "object-assign": "^4.1.0", "rimraf": "^2.5.2", "typescript": "^1.8.10", diff --git a/src/handlebars.ts b/src/handlebars.ts new file mode 100644 index 00000000..fb763a84 --- /dev/null +++ b/src/handlebars.ts @@ -0,0 +1,236 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +// Allow for running under nodejs/requirejs in tests +var _monaco: typeof monaco = (typeof monaco === 'undefined' ? (self).monaco : monaco); + +const EMPTY_ELEMENTS:string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr']; + +export var conf:IRichLanguageConfiguration = { + wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g, + + comments: { + blockComment: [''] + }, + + brackets: [ + [''], + ['{{', '}}'] + ], + + __electricCharacterSupport: { + embeddedElectricCharacters: ['*', '}', ']', ')'] + }, + + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: '\'', close: '\'' } + ], + + surroundingPairs: [ + { open: '<', close: '>' }, + { open: '"', close: '"' }, + { open: '\'', close: '\'' } + ], + + onEnterRules: [ + { + beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + afterText: /^<\/(\w[\w\d]*)\s*>$/i, + action: { indentAction: _monaco.languages.IndentAction.IndentOutdent } + }, + { + beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + action: { indentAction: _monaco.languages.IndentAction.Indent } + } + ], +} + +export const htmlTokenTypes = { + DELIM_START: 'start.delimiter.tag.html', + DELIM_END: 'end.delimiter.tag.html', + DELIM_COMMENT: 'comment.html', + COMMENT: 'comment.content.html', + getTag: (name: string) => { + return 'tag.html'; + } +}; + +export var language = { + defaultToken: '', + tokenPostfix: '', + // ignoreCase: true, + + // The main tokenizer for our languages + tokenizer: { + root: [ + [/\{\{/, { token: '@rematch', switchTo: '@handlebarsInSimpleState.root' }], + [/)/, [htmlTokenTypes.DELIM_START, 'tag.html', htmlTokenTypes.DELIM_END]], + [/(<)(script)/, [htmlTokenTypes.DELIM_START, { token: 'tag.html', next: '@script'} ]], + [/(<)(style)/, [htmlTokenTypes.DELIM_START, { token: 'tag.html', next: '@style'} ]], + [/(<)([:\w]+)/, [htmlTokenTypes.DELIM_START, { token: 'tag.html', next: '@otherTag'} ]], + [/(<\/)(\w+)/, [htmlTokenTypes.DELIM_START, { token: 'tag.html', next: '@otherTag' }]], + [/]+/, 'metatag.content.html' ], + [/>/, 'metatag.html', '@pop' ], + ], + + comment: [ + [/\{\{/, { token: '@rematch', switchTo: '@handlebarsInSimpleState.comment' }], + [/-->/, 'comment.html', '@pop'], + [/[^-]+/, 'comment.content.html'], + [/./, 'comment.content.html'] + ], + + otherTag: [ + [/\{\{/, { token: '@rematch', switchTo: '@handlebarsInSimpleState.otherTag' }], + [/\/?>/, htmlTokenTypes.DELIM_END, '@pop'], + [/"([^"]*)"/, 'attribute.value'], + [/'([^']*)'/, 'attribute.value'], + [/[\w\-]+/, 'attribute.name'], + [/=/, 'delimiter'], + [/[ \t\r\n]+/], // whitespace + ], + + // -- BEGIN ', + tokens: [ + { startIndex:0, type: HTML_DELIM_START }, + { startIndex:1, type: getTag('script') }, + { startIndex:7, type: '' }, + { startIndex:8, type: HTML_ATTRIB_NAME }, + { startIndex:12, type: DELIM_ASSIGN }, + { startIndex:13, type: HTML_ATTRIB_VALUE }, + { startIndex:41, type: HTML_DELIM_END }, + { startIndex:42, type: HTML_DELIM_START }, + { startIndex:43, type: getTag('h1') }, + { startIndex:45, type: HTML_DELIM_END }, + { startIndex:46, type: handlebarsTokenTypes.EMBED }, + { startIndex:48, type: '' }, + { startIndex:49, type: handlebarsTokenTypes.VARIABLE }, + { startIndex:54, type: '' }, + { startIndex:55, type: handlebarsTokenTypes.EMBED }, + { startIndex:57, type: HTML_DELIM_START }, + { startIndex:59, type: getTag('h1') }, + { startIndex:61, type: HTML_DELIM_END }, + { startIndex:62, type: HTML_DELIM_START }, + { startIndex:64, type: getTag('script') }, + { startIndex:70, type: HTML_DELIM_END } + ] + }], + + // Multi-line HTML Expressions + [{ + line: '', + tokens: [ + { startIndex:0, type: HTML_DELIM_START }, + { startIndex:2, type: getTag('script') }, + { startIndex:8, type: HTML_DELIM_END } + ] + }], + + // HTML Nested Modes + [{ + line: '{{foo}}{{bar}}', + tokens: [ + { startIndex:0, type: handlebarsTokenTypes.EMBED }, + { startIndex:2, type: handlebarsTokenTypes.VARIABLE }, + { startIndex:5, type: handlebarsTokenTypes.EMBED }, + { startIndex:7, type: HTML_DELIM_START }, + { startIndex:8, type: getTag('script') }, + { startIndex:14, type: HTML_DELIM_END }, + { startIndex:15, type: HTML_DELIM_START }, + { startIndex:17, type: getTag('script') }, + { startIndex:23, type: HTML_DELIM_END }, + { startIndex:24, type: handlebarsTokenTypes.EMBED }, + { startIndex:26, type: handlebarsTokenTypes.VARIABLE }, + { startIndex:29, type: handlebarsTokenTypes.EMBED } + ] + }], + + // else keyword + [{ + line: '{{else}}', + tokens: [ + { startIndex:0, type: handlebarsTokenTypes.EMBED }, + { startIndex:2, type: handlebarsTokenTypes.KEYWORD }, + { startIndex:6, type: handlebarsTokenTypes.EMBED } + ] + }], + + // else keyword #2 + [{ + line: '{{elseFoo}}', + tokens: [ + { startIndex:0, type: handlebarsTokenTypes.EMBED }, + { startIndex:2, type: handlebarsTokenTypes.VARIABLE }, + { startIndex:9, type: handlebarsTokenTypes.EMBED } + ] + }], + + // Token inside attribute + [{ + line: '', + tokens: [ + { startIndex:0, type: HTML_DELIM_START }, + { startIndex:1, type: getTag('a') }, + { startIndex:2, type: '' }, + { startIndex:3, type: HTML_ATTRIB_NAME }, + { startIndex:7, type: DELIM_ASSIGN }, + { startIndex:8, type: HTML_ATTRIB_VALUE }, + { startIndex:30, type: HTML_DELIM_END } + ] + }] +]); diff --git a/tsconfig.json b/tsconfig.json index 351910e2..1a724afa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ "src/dockerfile.ts", "src/fsharp.ts", "src/go.ts", + "src/handlebars.ts", "src/html.ts", "src/ini.ts", "src/jade.ts", @@ -46,6 +47,7 @@ "test/dockerfile.test.ts", "test/fsharp.test.ts", "test/go.test.ts", + "test/handlebars.test.ts", "test/html.test.ts", "test/jade.test.ts", "test/java.test.ts",