瀏覽代碼

Add razor

Alex Dima 8 年之前
父節點
當前提交
5d907f60f3
共有 8 個文件被更改,包括 498 次插入1 次删除
  1. 1 0
      README.md
  2. 1 0
      gulpfile.js
  3. 1 1
      package.json
  4. 7 0
      src/monaco.contribution.ts
  5. 329 0
      src/razor.ts
  6. 1 0
      test/all.js
  7. 156 0
      test/razor.test.ts
  8. 2 0
      tsconfig.json

+ 1 - 0
README.md

@@ -21,6 +21,7 @@ Colorization and configuration supports for multiple languages for the Monaco Ed
 * powershell
 * python
 * r
+* razor
 * ruby
 * sql
 * swift

+ 1 - 0
gulpfile.js

@@ -67,6 +67,7 @@ gulp.task('release', ['clean-release','compile'], function() {
 			bundleOne('src/postiats'),
 			bundleOne('src/python'),
 			bundleOne('src/r'),
+			bundleOne('src/razor'),
 			bundleOne('src/ruby'),
 			bundleOne('src/scss'),
 			bundleOne('src/sql'),

+ 1 - 1
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.3",
+    "monaco-editor-core": "0.7.0-next.4",
     "object-assign": "^4.1.0",
     "rimraf": "^2.5.2",
     "typescript": "^1.8.10",

+ 7 - 0
src/monaco.contribution.ts

@@ -185,6 +185,13 @@ registerLanguage({
 	aliases: [ 'R', 'r' ],
 	module: './r'
 });
+registerLanguage({
+	id: 'razor',
+	extensions: ['.cshtml'],
+	aliases: ['Razor', 'razor'],
+	mimetypes: ['text/x-cshtml'],
+	module: './razor'
+});
 registerLanguage({
 	id: 'ruby',
 	extensions: [ '.rb', '.rbx', '.rjs', '.gemspec', '.pp' ],

+ 329 - 0
src/razor.ts

@@ -0,0 +1,329 @@
+/*---------------------------------------------------------------------------------------------
+ *  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' ? (<any>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: '\'' }
+		],
+
+		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 = <ILanguage> {
+	defaultToken: '',
+	tokenPostfix: '',
+	// ignoreCase: true,
+
+	// The main tokenizer for our languages
+	tokenizer: {
+		root: [
+			[/@@/], // text
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.root' }],
+			[/<!DOCTYPE/, 'metatag.html', '@doctype'],
+			[/<!--/, 'comment.html', '@comment'],
+			[/(<)(\w+)(\/>)/, [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' }]],
+			[/</, htmlTokenTypes.DELIM_START],
+			[/[ \t\r\n]+/], // whitespace
+			[/[^<@]+/], // text
+		],
+
+		doctype: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.comment' }],
+			[/[^>]+/, 'metatag.content.html' ],
+			[/>/, 'metatag.html', '@pop' ],
+		],
+
+		comment: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.comment' }],
+			[/-->/, 'comment.html', '@pop'],
+			[/[^-]+/, 'comment.content.html'],
+			[/./, 'comment.content.html']
+		],
+
+		otherTag: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.otherTag' }],
+			[/\/?>/, htmlTokenTypes.DELIM_END, '@pop'],
+			[/"([^"]*)"/, 'attribute.value'],
+			[/'([^']*)'/, 'attribute.value'],
+			[/[\w\-]+/, 'attribute.name'],
+			[/=/, 'delimiter'],
+			[/[ \t\r\n]+/], // whitespace
+		],
+
+		// -- BEGIN <script> tags handling
+
+		// After <script
+		script: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.script' }],
+			[/type/, 'attribute.name', '@scriptAfterType'],
+			[/"([^"]*)"/, 'attribute.value'],
+			[/'([^']*)'/, 'attribute.value'],
+			[/[\w\-]+/, 'attribute.name'],
+			[/=/, 'delimiter'],
+			[/>/, { token: htmlTokenTypes.DELIM_END, next: '@scriptEmbedded.text/javascript', nextEmbedded: 'text/javascript'} ],
+			[/[ \t\r\n]+/], // whitespace
+			[/(<\/)(script\s*)(>)/, [ htmlTokenTypes.DELIM_START, 'tag.html', { token: htmlTokenTypes.DELIM_END, next: '@pop' } ]]
+		],
+
+		// After <script ... type
+		scriptAfterType: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.scriptAfterType' }],
+			[/=/,'delimiter', '@scriptAfterTypeEquals'],
+			[/[ \t\r\n]+/], // whitespace
+			[/<\/script\s*>/, { token: '@rematch', next: '@pop' }]
+		],
+
+		// After <script ... type =
+		scriptAfterTypeEquals: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.scriptAfterTypeEquals' }],
+			[/"([^"]*)"/, { token: 'attribute.value', switchTo: '@scriptWithCustomType.$1' } ],
+			[/'([^']*)'/, { token: 'attribute.value', switchTo: '@scriptWithCustomType.$1' } ],
+			[/[ \t\r\n]+/], // whitespace
+			[/<\/script\s*>/, { token: '@rematch', next: '@pop' }]
+		],
+
+		// After <script ... type = $S2
+		scriptWithCustomType: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.scriptWithCustomType.$S2' }],
+			[/>/, { token: htmlTokenTypes.DELIM_END, next: '@scriptEmbedded.$S2', nextEmbedded: '$S2'}],
+			[/"([^"]*)"/, 'attribute.value'],
+			[/'([^']*)'/, 'attribute.value'],
+			[/[\w\-]+/, 'attribute.name'],
+			[/=/, 'delimiter'],
+			[/[ \t\r\n]+/], // whitespace
+			[/<\/script\s*>/, { token: '@rematch', next: '@pop' }]
+		],
+
+		scriptEmbedded: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInEmbeddedState.scriptEmbedded.$S2', nextEmbedded: '@pop' }],
+			[/<\/script/, { token: '@rematch', next: '@pop', nextEmbedded: '@pop' }]
+		],
+
+		// -- END <script> tags handling
+
+
+		// -- BEGIN <style> tags handling
+
+		// After <style
+		style: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.style' }],
+			[/type/, 'attribute.name', '@styleAfterType'],
+			[/"([^"]*)"/, 'attribute.value'],
+			[/'([^']*)'/, 'attribute.value'],
+			[/[\w\-]+/, 'attribute.name'],
+			[/=/, 'delimiter'],
+			[/>/, { token: htmlTokenTypes.DELIM_END, next: '@styleEmbedded.text/css', nextEmbedded: 'text/css'} ],
+			[/[ \t\r\n]+/], // whitespace
+			[/(<\/)(style\s*)(>)/, [htmlTokenTypes.DELIM_START, 'tag.html', { token: htmlTokenTypes.DELIM_END, next: '@pop' } ]]
+		],
+
+		// After <style ... type
+		styleAfterType: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.styleAfterType' }],
+			[/=/,'delimiter', '@styleAfterTypeEquals'],
+			[/[ \t\r\n]+/], // whitespace
+			[/<\/style\s*>/, { token: '@rematch', next: '@pop' }]
+		],
+
+		// After <style ... type =
+		styleAfterTypeEquals: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.styleAfterTypeEquals' }],
+			[/"([^"]*)"/, { token: 'attribute.value', switchTo: '@styleWithCustomType.$1' } ],
+			[/'([^']*)'/, { token: 'attribute.value', switchTo: '@styleWithCustomType.$1' } ],
+			[/[ \t\r\n]+/], // whitespace
+			[/<\/style\s*>/, { token: '@rematch', next: '@pop' }]
+		],
+
+		// After <style ... type = $S2
+		styleWithCustomType: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInSimpleState.styleWithCustomType.$S2' }],
+			[/>/, { token: htmlTokenTypes.DELIM_END, next: '@styleEmbedded.$S2', nextEmbedded: '$S2'}],
+			[/"([^"]*)"/, 'attribute.value'],
+			[/'([^']*)'/, 'attribute.value'],
+			[/[\w\-]+/, 'attribute.name'],
+			[/=/, 'delimiter'],
+			[/[ \t\r\n]+/], // whitespace
+			[/<\/style\s*>/, { token: '@rematch', next: '@pop' }]
+		],
+
+		styleEmbedded: [
+			[/@[^@]/, { token: '@rematch', switchTo: '@razorInEmbeddedState.styleEmbedded.$S2', nextEmbedded: '@pop' }],
+			[/<\/style/, { token: '@rematch', next: '@pop', nextEmbedded: '@pop' }]
+		],
+
+		// -- END <style> tags handling
+
+
+		razorInSimpleState: [
+			[/@\*/, 'comment.cs', '@razorBlockCommentTopLevel'],
+			[/@[{(]/, 'metatag.cs', '@razorRootTopLevel'],
+			[/(@)(\s*[\w]+)/, ['metatag.cs', { token: 'identifier.cs', switchTo: '@$S2.$S3'} ]],
+			[/[})]/, { token: 'metatag.cs', switchTo: '@$S2.$S3' }],
+			[/\*@/, { token: 'comment.cs', switchTo: '@$S2.$S3' }],
+		],
+
+		razorInEmbeddedState: [
+			[/@\*/, 'comment.cs', '@razorBlockCommentTopLevel'],
+			[/@[{(]/, 'metatag.cs', '@razorRootTopLevel'],
+			[/(@)(\s*[\w]+)/, ['metatag.cs', { token: 'identifier.cs', switchTo: '@$S2.$S3', nextEmbedded: '$S3'} ]],
+			[/[})]/, { token: 'metatag.cs', switchTo: '@$S2.$S3', nextEmbedded: '$S3' }],
+			[/\*@/, { token: 'comment.cs', switchTo: '@$S2.$S3', nextEmbedded: '$S3' }],
+		],
+
+		razorBlockCommentTopLevel: [
+			[/\*@/, '@rematch', '@pop'],
+			[/[^*]+/, 'comment.cs'],
+			[/./, 'comment.cs']
+		],
+
+		razorBlockComment: [
+			[/\*@/, 'comment.cs', '@pop'],
+			[/[^*]+/, 'comment.cs'],
+			[/./, 'comment.cs']
+		],
+
+		razorRootTopLevel: [
+			[/\{/, 'delimiter.bracket.cs', '@razorRoot'] ,
+			[/\(/, 'delimiter.parenthesis.cs', '@razorRoot'] ,
+			[/[})]/, '@rematch', '@pop'],
+			{ include: 'razorCommon' }
+		],
+
+		razorRoot: [
+			[/\{/, 'delimiter.bracket.cs', '@razorRoot'] ,
+			[/\(/, 'delimiter.parenthesis.cs', '@razorRoot'] ,
+			[/\}/, 'delimiter.bracket.cs', '@pop'],
+			[/\)/, 'delimiter.parenthesis.cs', '@pop'],
+			{ include: 'razorCommon' }
+		],
+
+		razorCommon: [
+ 			[/[a-zA-Z_]\w*/, {
+				cases: {
+					'@razorKeywords': { token:'keyword.cs' },
+					'@default': 'identifier.cs'
+				}
+			}],
+
+			// brackets
+			[/[\[\]]/, 'delimiter.array.cs' ],
+
+			// whitespace
+			[/[ \t\r\n]+/],
+
+			// comments
+			[/\/\/.*$/, 'comment.cs'],
+			[/@\*/, 'comment.cs', '@razorBlockComment'],
+
+			// strings
+ 			[/"([^"]*)"/, 'string.cs'],
+			[/'([^']*)'/, 'string.cs'],
+
+			// simple html
+			[/(<)(\w+)(\/>)/, [htmlTokenTypes.DELIM_START, 'tag.html', htmlTokenTypes.DELIM_END]],
+			[/(<)(\w+)(>)/, [htmlTokenTypes.DELIM_START, 'tag.html', htmlTokenTypes.DELIM_END]],
+			[/(<\/)(\w+)(>)/, [htmlTokenTypes.DELIM_START, 'tag.html', htmlTokenTypes.DELIM_END]],
+
+			// delimiters
+			[/[\+\-\*\%\&\|\^\~\!\=\<\>\/\?\;\:\.\,]/, 'delimiter.cs' ],
+
+			// numbers
+			[/\d*\d+[eE]([\-+]?\d+)?/, 'number.float.cs'],
+			[/\d*\.\d+([eE][\-+]?\d+)?/, 'number.float.cs'],
+			[/0[xX][0-9a-fA-F']*[0-9a-fA-F]/, 'number.hex.cs'],
+			[/0[0-7']*[0-7]/, 'number.octal.cs'],
+			[/0[bB][0-1']*[0-1]/, 'number.binary.cs'],
+			[/\d[\d']*/, 'number.cs'],
+			[/\d/, 'number.cs'],
+		]
+	},
+
+	razorKeywords: [
+		'abstract', 'as', 'async', 'await', 'base', 'bool',
+		'break', 'by', 'byte', 'case',
+		'catch', 'char', 'checked', 'class',
+		'const', 'continue', 'decimal', 'default',
+		'delegate', 'do', 'double', 'descending',
+		'explicit', 'event', 'extern', 'else',
+		'enum', 'false', 'finally', 'fixed',
+		'float', 'for', 'foreach', 'from',
+		'goto', 'group', 'if', 'implicit',
+		'in', 'int', 'interface', 'internal',
+		'into', 'is', 'lock', 'long', 'nameof',
+		'new', 'null', 'namespace', 'object',
+		'operator', 'out', 'override', 'orderby',
+		'params', 'private', 'protected', 'public',
+		'readonly', 'ref', 'return', 'switch',
+		'struct', 'sbyte', 'sealed', 'short',
+		'sizeof', 'stackalloc', 'static', 'string',
+		'select', 'this', 'throw', 'true',
+		'try', 'typeof', 'uint', 'ulong',
+		'unchecked', 'unsafe', 'ushort', 'using',
+		'var', 'virtual', 'volatile', 'void', 'when',
+		'while', 'where', 'yield',
+		'model', 'inject' // Razor specific
+	],
+
+	escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
+
+};

+ 1 - 0
test/all.js

@@ -42,6 +42,7 @@ requirejs([
 		'out/test/powershell.test',
 		'out/test/python.test',
 		'out/test/r.test',
+		'out/test/razor.test',
 		'out/test/ruby.test',
 		'out/test/swift.test',
 		'out/test/sql.test',

+ 156 - 0
test/razor.test.ts

@@ -0,0 +1,156 @@
+/*---------------------------------------------------------------------------------------------
+ *  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 './testRunner';
+import {htmlTokenTypes} from '../src/php';
+
+const EMBED_CS = 'metatag.cs';
+
+testTokenization('razor', [
+
+	// Embedding - embedded html
+	[{
+	line: '@{ var x; <b>x</b> }',
+	tokens: [
+		{ startIndex: 0, type: EMBED_CS },
+		{ startIndex: 2, type: '' },
+		{ startIndex: 3, type: 'keyword.cs' },
+		{ startIndex: 6, type: '' },
+		{ startIndex: 7, type: 'identifier.cs' },
+		{ startIndex: 8, type: 'delimiter.cs' },
+		{ startIndex: 9, type: '' },
+		{ startIndex: 10, type: htmlTokenTypes.DELIM_START },
+		{ startIndex: 11, type: htmlTokenTypes.getTag('b') },
+		{ startIndex: 12, type: htmlTokenTypes.DELIM_END },
+		{ startIndex: 13, type: 'identifier.cs' },
+		{ startIndex: 14, type: htmlTokenTypes.DELIM_START },
+		{ startIndex: 16, type: htmlTokenTypes.getTag('b') },
+		{ startIndex: 17, type: htmlTokenTypes.DELIM_END },
+		{ startIndex: 18, type: '' },
+		{ startIndex: 19, type: EMBED_CS }
+	]}],
+
+	// Comments - razor comment inside csharp
+	[{
+	line: '@{ var x; @* comment *@ x= 0; }',
+	tokens: [
+		{ startIndex: 0, type: EMBED_CS },
+		{ startIndex: 2, type: '' },
+		{ startIndex: 3, type: 'keyword.cs' },
+		{ startIndex: 6, type: '' },
+		{ startIndex: 7, type: 'identifier.cs' },
+		{ startIndex: 8, type: 'delimiter.cs' },
+		{ startIndex: 9, type: '' },
+		{ startIndex: 10, type: 'comment.cs' },
+		{ startIndex: 23, type: '' },
+		{ startIndex: 24, type: 'identifier.cs' },
+		{ startIndex: 25, type: 'delimiter.cs' },
+		{ startIndex: 26, type: '' },
+		{ startIndex: 27, type: 'number.cs' },
+		{ startIndex: 28, type: 'delimiter.cs' },
+		{ startIndex: 29, type: '' },
+		{ startIndex: 30, type: EMBED_CS }
+	]}],
+
+	// Blocks - simple
+	[{
+	line: '@{ var total = 0; }',
+	tokens: [
+		{ startIndex: 0, type: EMBED_CS },
+		{ startIndex: 2, type: '' },
+		{ startIndex: 3, type: 'keyword.cs' },
+		{ startIndex: 6, type: '' },
+		{ startIndex: 7, type: 'identifier.cs' },
+		{ startIndex: 12, type: '' },
+		{ startIndex: 13, type: 'delimiter.cs' },
+		{ startIndex: 14, type: '' },
+		{ startIndex: 15, type: 'number.cs' },
+		{ startIndex: 16, type: 'delimiter.cs' },
+		{ startIndex: 17, type: '' },
+		{ startIndex: 18, type: EMBED_CS }
+	]}],
+
+	// [{
+	// line: '@if(true){ var total = 0; }',
+	// tokens: [
+	// 	{ startIndex: 0, type: EMBED_CS },
+	// 	{ startIndex: 1, type: 'keyword.cs' },
+	// 	{ startIndex: 3, type: 'punctuation.parenthesis.cs' },
+	// 	{ startIndex: 4, type: 'keyword.cs' },
+	// 	{ startIndex: 8, type: 'punctuation.parenthesis.cs' },
+	// 	{ startIndex: 9, type: EMBED_CS },
+	// 	{ startIndex: 10, type: '' },
+	// 	{ startIndex: 11, type: 'keyword.cs' },
+	// 	{ startIndex: 14, type: '' },
+	// 	{ startIndex: 15, type: 'identifier.cs' },
+	// 	{ startIndex: 20, type: '' },
+	// 	{ startIndex: 21, type: 'delimiter.cs' },
+	// 	{ startIndex: 22, type: '' },
+	// 	{ startIndex: 23, type: 'number.cs' },
+	// 	{ startIndex: 24, type: 'delimiter.cs' },
+	// 	{ startIndex: 25, type: '' },
+	// 	{ startIndex: 26, type: EMBED_CS }
+	// ]}],
+
+	// Expressions - csharp expressions in html
+	[{
+	line: 'test@xyz<br>',
+	tokens: [
+		{ startIndex:0, type: '' },
+		{ startIndex:4, type: EMBED_CS },
+		{ startIndex:5, type: 'identifier.cs' },
+		{ startIndex:8, type: htmlTokenTypes.DELIM_START },
+		{ startIndex:9, type: htmlTokenTypes.getTag('br') },
+		{ startIndex:11, type: htmlTokenTypes.DELIM_END }
+	]}],
+
+	[{
+	line: 'test@xyz',
+	tokens: [
+		{ startIndex:0, type: '' },
+		{ startIndex:4, type: EMBED_CS },
+		{ startIndex:5, type: 'identifier.cs' }
+	]}],
+
+	[{
+	line: 'test @ xyz',
+	tokens: [
+		{ startIndex: 0, type: '' },
+		{ startIndex: 5, type: EMBED_CS },
+		{ startIndex: 6, type: 'identifier.cs' }
+	]}],
+
+	[{
+	line: 'test @(foo) xyz',
+	tokens: [
+		{ startIndex:0, type: '' },
+		{ startIndex:5, type: EMBED_CS },
+		{ startIndex:7, type: 'identifier.cs' },
+		{ startIndex:10, type: EMBED_CS },
+		{ startIndex:11, type: '' }
+	]}],
+
+	[{
+	line: 'test @(foo(\")\")) xyz',
+	tokens: [
+		{ startIndex:0, type: '' },
+		{ startIndex:5, type: EMBED_CS },
+		{ startIndex:7, type: 'identifier.cs' },
+		{ startIndex:10, type: 'delimiter.parenthesis.cs' },
+		{ startIndex:11, type: 'string.cs' },
+		{ startIndex:14, type: 'delimiter.parenthesis.cs' },
+		{ startIndex:15, type: EMBED_CS },
+		{ startIndex:16, type: '' }
+	]}],
+
+	// Escaping - escaped at character
+	[{
+	line: 'test@@xyz',
+	tokens: [
+		{ startIndex:0, type: '' }
+	]}]
+]);

+ 2 - 0
tsconfig.json

@@ -33,6 +33,7 @@
     "src/powershell.ts",
     "src/python.ts",
     "src/r.ts",
+    "src/razor.ts",
     "src/ruby.ts",
     "src/scss.ts",
     "src/sql.ts",
@@ -60,6 +61,7 @@
     "test/powershell.test.ts",
     "test/python.test.ts",
     "test/r.test.ts",
+    "test/razor.test.ts",
     "test/ruby.test.ts",
     "test/sql.test.ts",
     "test/swift.test.ts",