diff --git a/gulpfile.js b/gulpfile.js index 1b8564dc..1fa6e996 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -46,6 +46,7 @@ gulp.task('release', ['clean-release','compile'], function() { return merge( bundleOne('src/monaco.contribution'), bundleOne('src/bat'), + bundleOne('src/css'), bundleOne('src/coffee'), bundleOne('src/cpp'), bundleOne('src/csharp'), @@ -55,6 +56,7 @@ gulp.task('release', ['clean-release','compile'], function() { bundleOne('src/ini'), bundleOne('src/jade'), bundleOne('src/java'), + bundleOne('src/less'), bundleOne('src/lua'), bundleOne('src/markdown'), bundleOne('src/objective-c'), @@ -62,6 +64,7 @@ gulp.task('release', ['clean-release','compile'], function() { bundleOne('src/python'), bundleOne('src/r'), bundleOne('src/ruby'), + bundleOne('src/scss'), bundleOne('src/sql'), bundleOne('src/swift'), bundleOne('src/vb'), diff --git a/package.json b/package.json index fefbb0ea..1c8585ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "monaco-languages", - "version": "0.2.0", + "version": "0.2.1", "description": "Bundle of many languages for the Monaco Editor.", "scripts": { "test": "node_modules/.bin/mocha", diff --git a/src/css.ts b/src/css.ts new file mode 100644 index 00000000..d30efd8c --- /dev/null +++ b/src/css.ts @@ -0,0 +1,190 @@ +/*--------------------------------------------------------------------------------------------- + * 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 LanguageConfiguration = monaco.languages.LanguageConfiguration; +import IMonarchLanguage = monaco.languages.IMonarchLanguage; + +export var conf:LanguageConfiguration = { + wordPattern: /(#?-?\d*\.\d\w*%?)|((::|[@#.!:])?[\w-?]+%?)|::|[@#.!:]/g, + + comments: { + blockComment: ['/*', '*/'] + }, + + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"', notIn: ['string'] }, + { open: '\'', close: '\'', notIn: ['string'] } + ] +}; + +const TOKEN_SELECTOR = 'entity.name.selector'; +const TOKEN_SELECTOR_TAG = 'entity.name.tag'; +const TOKEN_PROPERTY = 'support.type.property-name'; +const TOKEN_VALUE = 'support.property-value'; +const TOKEN_AT_KEYWORD = 'keyword.control.at-rule'; + +export var language = { + defaultToken: '', + tokenPostfix: '.css', + + ws: '[ \t\n\r\f]*', // whitespaces (referenced in several rules) + identifier: '-?-?([a-zA-Z]|(\\\\(([0-9a-fA-F]{1,6}\\s?)|[^[0-9a-fA-F])))([\\w\\-]|(\\\\(([0-9a-fA-F]{1,6}\\s?)|[^[0-9a-fA-F])))*', + + brackets: [ + { open: '{', close: '}', token: 'punctuation.curly' }, + { open: '[', close: ']', token: 'punctuation.bracket' }, + { open: '(', close: ')', token: 'punctuation.parenthesis' }, + { open: '<', close: '>', token: 'punctuation.angle' } + ], + + tokenizer: { + root: [ + { include: '@selector' }, + ], + + selector: [ + { include: '@comments' }, + { include: '@import' }, + ['[@](keyframes|-webkit-keyframes|-moz-keyframes|-o-keyframes)', { token: TOKEN_AT_KEYWORD, next: '@keyframedeclaration' }], + ['[@](page|content|font-face|-moz-document)', { token: TOKEN_AT_KEYWORD }], + ['[@](charset|namespace)', { token: TOKEN_AT_KEYWORD, next: '@declarationbody' }], + ['url(\\-prefix)?\\(', { token: 'support.function.name', bracket: '@open', next: '@urldeclaration' }], + { include: '@selectorname' }, + ['[\\*]', TOKEN_SELECTOR_TAG], // selector symbols + ['[>\\+,]', 'punctuation'], // selector operators + ['\\[', { token: 'punctuation.bracket', bracket: '@open', next: '@selectorattribute' }], + ['{', { token: 'punctuation.curly', bracket: '@open', next: '@selectorbody' }] + ], + + selectorbody: [ + ['[*_]?@identifier@ws:(?=(\\s|\\d|[^{;}]*[;}]))', TOKEN_PROPERTY, '@rulevalue'], // rule definition: to distinguish from a nested selector check for whitespace, number or a semicolon + ['}', { token: 'punctuation.curly', bracket: '@close', next: '@pop' }] + ], + + selectorname: [ + ['(\\.|#(?=[^{])|%|(@identifier)|:)+', TOKEN_SELECTOR], // selector (.foo, div, ...) + ], + + selectorattribute: [ + { include: '@term' }, + [']', { token: 'punctuation.bracket', bracket: '@close', next: '@pop' }], + ], + + term: [ + { include: '@comments' }, + ['url(\\-prefix)?\\(', { token: 'support.function.name', bracket: '@open', next: '@urldeclaration' }], + { include: '@functioninvocation' }, + { include: '@numbers' }, + { include: '@name' }, + ['([<>=\\+\\-\\*\\/\\^\\|\\~,])', 'keyword.operator'], + [',', 'punctuation'] + ], + + rulevalue: [ + { include: '@term' }, + ['!important', 'literal'], + [';', 'punctuation', '@pop'], + ['(?=})', { token: '', next: '@pop' }] // missing semicolon + ], + + warndebug: [ + ['[@](warn|debug)', { token: TOKEN_AT_KEYWORD, next: '@declarationbody' }] + ], + + import: [ + ['[@](import)', { token: TOKEN_AT_KEYWORD, next: '@declarationbody' }] + ], + + urldeclaration: [ + { include: '@strings' }, + ['[^)\r\n]+', 'string'], + ['\\)', { token: 'support.function.name', bracket: '@close', next: '@pop' }] + ], + + parenthizedterm: [ + { include: '@term' }, + ['\\)', { token: 'punctuation.parenthesis', bracket: '@close', next: '@pop' }] + ], + + declarationbody: [ + { include: '@term' }, + [';', 'punctuation', '@pop'], + ['(?=})', { token: '', next: '@pop' }] // missing semicolon + ], + + comments: [ + ['\\/\\*', 'comment', '@comment'], + ['\\/\\/+.*', 'comment'] + ], + + comment: [ + ['\\*\\/', 'comment', '@pop'], + ['.', 'comment'] + ], + + name: [ + ['@identifier', TOKEN_VALUE] + ], + + numbers: [ + ['(\\d*\\.)?\\d+([eE][\\-+]?\\d+)?', { token: 'constant.numeric', next: '@units' }], + ['#[0-9a-fA-F_]+(?!\\w)', 'constant.rgb-value'] + ], + + units: [ + ['(em|ex|ch|rem|vw|vh|vm|cm|mm|in|px|pt|pc|deg|grad|rad|turn|s|ms|Hz|kHz|%)?', 'constant.numeric', '@pop'] + ], + + keyframedeclaration: [ + ['@identifier', 'support.function.name'], + ['{', { token: 'punctuation.curly', bracket: '@open', switchTo: '@keyframebody' }], + ], + + keyframebody: [ + { include: '@term' }, + ['{', { token: 'punctuation.curly', bracket: '@open', next: '@selectorbody' }], + ['}', { token: 'punctuation.curly', bracket: '@close', next: '@pop' }], + ], + + functioninvocation: [ + ['@identifier\\(', { token: 'support.function.name', bracket: '@open', next: '@functionarguments' }], + ], + + functionarguments: [ + ['\\$@identifier@ws:', TOKEN_PROPERTY], + ['[,]', 'punctuation'], + { include: '@term' }, + ['\\)', { token: 'support.function.name', bracket: '@close', next: '@pop' }], + ], + + strings: [ + ['~?"', { token: 'string.punctuation', bracket: '@open', next: '@stringenddoublequote' }], + ['~?\'', { token: 'string.punctuation', bracket: '@open', next: '@stringendquote' }] + ], + + stringenddoublequote: [ + ['\\\\.', 'string'], + ['"', { token: 'string.punctuation', next: '@pop', bracket: '@close' }], + ['.', 'string'] + ], + + stringendquote: [ + ['\\\\.', 'string'], + ['\'', { token: 'string.punctuation', next: '@pop', bracket: '@close' }], + ['.', 'string'] + ] + } +}; \ No newline at end of file diff --git a/src/less.ts b/src/less.ts new file mode 100644 index 00000000..a15590ed --- /dev/null +++ b/src/less.ts @@ -0,0 +1,175 @@ +/*--------------------------------------------------------------------------------------------- + * 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 var lessLanguageConfiguration: IRichLanguageConfiguration = { + wordPattern: /(#?-?\d*\.\d\w*%?)|([@#!.:]?[\w-?]+%?)|[@#!.]/g, + comments: { + blockComment: ['/*', '*/'], + lineComment: '//' + }, + brackets: [['{','}'], ['[',']'], ['(',')'], ['<','>']], + autoClosingPairs: [ + { open: '"', close: '"', notIn: ['string', 'comment'] }, + { open: '\'', close: '\'', notIn: ['string', 'comment'] }, + { open: '{', close: '}', notIn: ['string', 'comment'] }, + { open: '[', close: ']', notIn: ['string', 'comment'] }, + { open: '(', close: ')', notIn: ['string', 'comment'] }, + { open: '<', close: '>', notIn: ['string', 'comment'] }, + ] +}; + +const TOKEN_SELECTOR = 'entity.name.selector'; +const TOKEN_SELECTOR_TAG = 'entity.name.tag'; +const TOKEN_PROPERTY = 'support.type.property-name'; +const TOKEN_VALUE = 'support.property-value'; +const TOKEN_AT_KEYWORD = 'keyword.control.at-rule'; + +export var language = { + defaultToken: '', + tokenPostfix: '.less', + + identifier: '-?-?([a-zA-Z]|(\\\\(([0-9a-fA-F]{1,6}\\s?)|[^[0-9a-fA-F])))([\\w\\-]|(\\\\(([0-9a-fA-F]{1,6}\\s?)|[^[0-9a-fA-F])))*', + identifierPlus: '-?-?([a-zA-Z:.]|(\\\\(([0-9a-fA-F]{1,6}\\s?)|[^[0-9a-fA-F])))([\\w\\-:.]|(\\\\(([0-9a-fA-F]{1,6}\\s?)|[^[0-9a-fA-F])))*', + + brackets: [ + { open: '{', close: '}', token: 'punctuation.curly' }, + { open: '[', close: ']', token: 'punctuation.bracket' }, + { open: '(', close: ')', token: 'punctuation.parenthesis' }, + { open: '<', close: '>', token: 'punctuation.angle' } + ], + + tokenizer: { + root: [ + { include: '@nestedJSBegin' }, + + ['[ \\t\\r\\n]+', ''], + + { include: '@comments' }, + { include: '@keyword' }, + { include: '@strings' }, + { include: '@numbers' }, + ['[*_]?[a-zA-Z\\-\\s]+(?=:.*(;|(\\\\$)))', TOKEN_PROPERTY, '@attribute'], + + ['url(\\-prefix)?\\(', { token: 'function', bracket: '@open', next: '@urldeclaration'}], + + ['[{}()\\[\\]]', '@brackets'], + ['[,:;]', 'punctuation'], + + ['#@identifierPlus', TOKEN_SELECTOR + '.id'], + ['&', TOKEN_SELECTOR_TAG], + + ['\\.@identifierPlus(?=\\()', TOKEN_SELECTOR + '.class', '@attribute'], + ['\\.@identifierPlus', TOKEN_SELECTOR + '.class'], + + ['@identifierPlus', TOKEN_SELECTOR_TAG], + { include: '@operators' }, + + ['@(@identifier(?=[:,\\)]))', 'variable', '@attribute'], + ['@(@identifier)', 'variable'], + ['@', 'key', '@atRules'] + ], + + nestedJSBegin: [ + ['``', 'punctuation.backtick'], + ['`', { token: 'punctuation.backtick', bracket: '@open', next: '@nestedJSEnd', nextEmbedded: 'text/javascript' }], + ], + + nestedJSEnd: [ + ['`', { token: 'punctuation.backtick', bracket: '@close', next: '@pop' }], + ['.', { token: '@rematch', next: '@javascript_block' }], + ], + + javascript_block: [ + ['`', { token: '@rematch', next: '@pop', nextEmbedded: '@pop' }], + ], + + operators: [ + ['[<>=\\+\\-\\*\\/\\^\\|\\~]', 'operator'] + ], + + keyword: [ + ['(@[\\s]*import|![\\s]*important|true|false|when|iscolor|isnumber|isstring|iskeyword|isurl|ispixel|ispercentage|isem|hue|saturation|lightness|alpha|lighten|darken|saturate|desaturate|fadein|fadeout|fade|spin|mix|round|ceil|floor|percentage)\\b', 'keyword'] + ], + + urldeclaration: [ + { include: '@strings'}, + [ '[^)\r\n]+', 'string' ], + ['\\)', { token: 'tag', bracket: '@close', next: '@pop'}], + ], + + attribute: [ + { include: '@nestedJSBegin' }, + { include: '@comments' }, + { include: '@strings' }, + { include: '@numbers' }, + + { include: '@keyword' }, + + ['[a-zA-Z\\-]+(?=\\()', TOKEN_VALUE, '@attribute'], + ['>', 'operator', '@pop'], + ['@identifier', TOKEN_VALUE], + { include: '@operators' }, + ['@(@identifier)', 'variable'], + + ['[)\\}]', '@brackets', '@pop'], + ['[{}()\\[\\]>]', '@brackets'], + + ['[;]', 'punctuation', '@pop'], + ['[,=:]', 'punctuation'], + + ['\\s', ''], + ['.', TOKEN_VALUE] + ], + + comments: [ + ['\\/\\*', 'comment', '@comment'], + ['\\/\\/+.*', 'comment'], + ], + + comment: [ + ['\\*\\/', 'comment', '@pop'], + ['.', 'comment'], + ], + + numbers: [ + ['(\\d*\\.)?\\d+([eE][\\-+]?\\d+)?', { token: TOKEN_VALUE + '.numeric', next: '@units' }], + ['#[0-9a-fA-F_]+(?!\\w)', TOKEN_VALUE + '.rgb-value'] + ], + + units: [ + ['((em|ex|ch|rem|vw|vh|vm|cm|mm|in|px|pt|pc|deg|grad|rad|turn|s|ms|Hz|kHz|%)\\b)?', TOKEN_VALUE + '.unit', '@pop'] + ], + + strings: [ + ['~?"', { token: 'string.punctuation', bracket: '@open', next: '@stringsEndDoubleQuote' }], + ['~?\'', { token: 'string.punctuation', bracket: '@open', next: '@stringsEndQuote' }] + ], + + stringsEndDoubleQuote: [ + ['\\\\"', 'string'], + ['"', { token: 'string.punctuation', next: '@popall', bracket: '@close' }], + ['.', 'string'] + ], + + stringsEndQuote: [ + ['\\\\\'', 'string'], + ['\'', { token: 'string.punctuation', next: '@popall', bracket: '@close' }], + ['.', 'string'] + ], + + atRules: [ + { include: '@comments' }, + { include: '@strings' }, + ['[()]', 'punctuation'], + ['[\\{;]', 'punctuation', '@pop'], + ['.', 'key'] + ] + } +}; \ No newline at end of file diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index 0075fc25..c6a34622 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -183,3 +183,24 @@ registerLanguage({ mimetypes: ['text/xml', 'application/xml', 'application/xaml+xml', 'application/xml-dtd'], module: './xml' }); +registerLanguage({ + id: 'less', + extensions: ['.less'], + aliases: ['Less', 'less'], + mimetypes: ['text/x-less', 'text/less'], + module: './less' +}); +registerLanguage({ + id: 'scss', + extensions: ['.scss'], + aliases: ['Sass', 'sass', 'scss'], + mimetypes: ['text/x-scss', 'text/scss'], + module: './scss' +}); +registerLanguage({ + id: 'css', + extensions: ['.css'], + aliases: ['CSS', 'css'], + mimetypes: ['text/css'], + module: './css' +}); diff --git a/src/scss.ts b/src/scss.ts new file mode 100644 index 00000000..59bed50c --- /dev/null +++ b/src/scss.ts @@ -0,0 +1,277 @@ +/*--------------------------------------------------------------------------------------------- + * 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 LanguageConfiguration = monaco.languages.LanguageConfiguration; +import IMonarchLanguage = monaco.languages.IMonarchLanguage; + +export var conf: LanguageConfiguration = { + wordPattern: /(#?-?\d*\.\d\w*%?)|([@#!.:]?[\w-?]+%?)|[@#!.]/g, + comments: { + blockComment: ['/*', '*/'], + lineComment: '//' + }, + brackets: [['{', '}'], ['[', ']'], ['(', ')'], ['<', '>']], + autoClosingPairs: [ + { open: '"', close: '"', notIn: ['string', 'comment'] }, + { open: '\'', close: '\'', notIn: ['string', 'comment'] }, + { open: '{', close: '}', notIn: ['string', 'comment'] }, + { open: '[', close: ']', notIn: ['string', 'comment'] }, + { open: '(', close: ')', notIn: ['string', 'comment'] }, + { open: '<', close: '>', notIn: ['string', 'comment'] }, + ] +}; + +const TOKEN_SELECTOR = 'entity.name.selector'; +const TOKEN_SELECTOR_TAG = 'entity.name.tag'; +const TOKEN_PROPERTY = 'support.type.property-name'; +const TOKEN_VALUE = 'support.property-value'; +const TOKEN_AT_KEYWORD = 'keyword.control.at-rule'; + +export var language = { + defaultToken: '', + tokenPostfix: '.scss', + + ws: '[ \t\n\r\f]*', // whitespaces (referenced in several rules) + identifier: '-?-?([a-zA-Z]|(\\\\(([0-9a-fA-F]{1,6}\\s?)|[^[0-9a-fA-F])))([\\w\\-]|(\\\\(([0-9a-fA-F]{1,6}\\s?)|[^[0-9a-fA-F])))*', + + brackets: [ + { open: '{', close: '}', token: 'punctuation.curly' }, + { open: '[', close: ']', token: 'punctuation.bracket' }, + { open: '(', close: ')', token: 'punctuation.parenthesis' }, + { open: '<', close: '>', token: 'punctuation.angle' } + ], + + tokenizer: { + root: [ + { include: '@selector' }, + ], + + selector: [ + { include: '@comments' }, + { include: '@import' }, + { include: '@variabledeclaration' }, + { include: '@warndebug' }, // sass: log statements + ['[@](include)', { token: TOKEN_AT_KEYWORD, next: '@includedeclaration' }], // sass: include statement + ['[@](keyframes|-webkit-keyframes|-moz-keyframes|-o-keyframes)', { token: TOKEN_AT_KEYWORD, next: '@keyframedeclaration' }], + ['[@](page|content|font-face|-moz-document)', { token: TOKEN_AT_KEYWORD }], // sass: placeholder for includes + ['[@](charset|namespace)', { token: TOKEN_AT_KEYWORD, next: '@declarationbody' }], + ['[@](function)', { token: TOKEN_AT_KEYWORD, next: '@functiondeclaration' }], + ['[@](mixin)', { token: TOKEN_AT_KEYWORD, next: '@mixindeclaration' }], + ['url(\\-prefix)?\\(', { token: 'support.function.name', bracket: '@open', next: '@urldeclaration' }], + { include: '@controlstatement' }, // sass control statements + { include: '@selectorname' }, + ['[&\\*]', TOKEN_SELECTOR_TAG], // selector symbols + ['[>\\+,]', 'punctuation'], // selector operators + ['\\[', { token: 'punctuation.bracket', bracket: '@open', next: '@selectorattribute' }], + ['{', { token: 'punctuation.curly', bracket: '@open', next: '@selectorbody' }], + ], + + selectorbody: [ + ['[*_]?@identifier@ws:(?=(\\s|\\d|[^{;}]*[;}]))', TOKEN_PROPERTY, '@rulevalue'], // rule definition: to distinguish from a nested selector check for whitespace, number or a semicolon + { include: '@selector' }, // sass: nested selectors + ['[@](extend)', { token: TOKEN_AT_KEYWORD, next: '@extendbody' }], // sass: extend other selectors + ['[@](return)', { token: TOKEN_AT_KEYWORD, next: '@declarationbody' }], + ['}', { token: 'punctuation.curly', bracket: '@close', next: '@pop' }], + ], + + selectorname: [ + ['#{', { token: 'support.function.interpolation', bracket: '@open', next: '@variableinterpolation' }], // sass: interpolation + ['(\\.|#(?=[^{])|%|(@identifier)|:)+', TOKEN_SELECTOR], // selector (.foo, div, ...) + ], + + selectorattribute: [ + { include: '@term' }, + [']', { token: 'punctuation.bracket', bracket: '@close', next: '@pop' }], + ], + + term: [ + { include: '@comments' }, + ['url(\\-prefix)?\\(', { token: 'support.function.name', bracket: '@open', next: '@urldeclaration' }], + { include: '@functioninvocation' }, + { include: '@numbers' }, + { include: '@strings' }, + { include: '@variablereference' }, + ['(and\\b|or\\b|not\\b)', 'keyword.operator'], + { include: '@name' }, + ['([<>=\\+\\-\\*\\/\\^\\|\\~,])', 'keyword.operator'], + [',', 'punctuation'], + ['!default', 'literal'], + ['\\(', { token: 'punctuation.parenthesis', bracket: '@open', next: '@parenthizedterm' }], + ], + + rulevalue: [ + { include: '@term' }, + ['!important', 'literal'], + [';', 'punctuation', '@pop'], + ['{', { token: 'punctuation.curly', bracket: '@open', switchTo: '@nestedproperty' }], // sass: nested properties + ['(?=})', { token: '', next: '@pop' }], // missing semicolon + ], + + nestedproperty: [ + ['[*_]?@identifier@ws:', TOKEN_PROPERTY, '@rulevalue'], + { include: '@comments' }, + ['}', { token: 'punctuation.curly', bracket: '@close', next: '@pop' }], + ], + + warndebug: [ + ['[@](warn|debug)', { token: TOKEN_AT_KEYWORD, next: '@declarationbody' }], + ], + + import: [ + ['[@](import)', { token: TOKEN_AT_KEYWORD, next: '@declarationbody' }], + ], + + variabledeclaration: [ // sass variables + ['\\$@identifier@ws:', 'variable.decl', '@declarationbody'], + ], + + urldeclaration: [ + { include: '@strings' }, + ['[^)\r\n]+', 'string'], + ['\\)', { token: 'support.function.name', bracket: '@close', next: '@pop' }], + ], + + parenthizedterm: [ + { include: '@term' }, + ['\\)', { token: 'punctuation.parenthesis', bracket: '@close', next: '@pop' }], + ], + + declarationbody: [ + { include: '@term' }, + [';', 'punctuation', '@pop'], + ['(?=})', { token: '', next: '@pop' }], // missing semicolon + ], + + extendbody: [ + { include: '@selectorname' }, + ['!optional', 'literal'], + [';', 'punctuation', '@pop'], + ['(?=})', { token: '', next: '@pop' }], // missing semicolon + ], + + variablereference: [ // sass variable reference + ['\\$@identifier', 'variable.ref'], + ['\\.\\.\\.', 'keyword.operator'], // var args in reference + ['#{', { token: 'support.function.interpolation', bracket: '@open', next: '@variableinterpolation' }], // sass var resolve + ], + + variableinterpolation: [ + { include: '@variablereference' }, + ['}', { token: 'support.function.interpolation', bracket: '@close', next: '@pop' }], + ], + + comments: [ + ['\\/\\*', 'comment', '@comment'], + ['\\/\\/+.*', 'comment'], + ], + + comment: [ + ['\\*\\/', 'comment', '@pop'], + ['.', 'comment'], + ], + + name: [ + ['@identifier', TOKEN_VALUE], + ], + + numbers: [ + ['(\\d*\\.)?\\d+([eE][\\-+]?\\d+)?', { token: 'constant.numeric', next: '@units' }], + ['#[0-9a-fA-F_]+(?!\\w)', 'constant.rgb-value'], + ], + + units: [ + ['(em|ex|ch|rem|vw|vh|vm|cm|mm|in|px|pt|pc|deg|grad|rad|turn|s|ms|Hz|kHz|%)?', 'constant.numeric', '@pop'] + ], + + functiondeclaration: [ + ['@identifier@ws\\(', { token: 'support.function.name', bracket: '@open', next: '@parameterdeclaration' }], + ['{', { token: 'punctuation.curly', bracket: '@open', switchTo: '@functionbody' }], + ], + + mixindeclaration: [ + // mixin with parameters + ['@identifier@ws\\(', { token: 'support.function.name', bracket: '@open', next: '@parameterdeclaration' }], + // mixin without parameters + ['@identifier', 'support.function.name'], + ['{', { token: 'punctuation.curly', bracket: '@open', switchTo: '@selectorbody' }], + ], + + parameterdeclaration: [ + ['\\$@identifier@ws:', 'variable'], + ['\\.\\.\\.', 'keyword.operator'], // var args in declaration + [',', 'punctuation'], + { include: '@term' }, + ['\\)', { token: 'support.function.name', bracket: '@close', next: '@pop' }], + ], + + includedeclaration: [ + { include: '@functioninvocation' }, + ['@identifier', 'support.function.name'], + [';', 'punctuation', '@pop'], + ['(?=})', { token: '', next: '@pop' }], // missing semicolon + ['{', { token: 'punctuation.curly', bracket: '@open', switchTo: '@selectorbody' }], + ], + + keyframedeclaration: [ + ['@identifier', 'support.function.name'], + ['{', { token: 'punctuation.curly', bracket: '@open', switchTo: '@keyframebody' }], + ], + + keyframebody: [ + { include: '@term' }, + ['{', { token: 'punctuation.curly', bracket: '@open', next: '@selectorbody' }], + ['}', { token: 'punctuation.curly', bracket: '@close', next: '@pop' }], + ], + + controlstatement: [ + ['[@](if|else|for|while|each|media)', { token: 'keyword.flow.control.at-rule', next: '@controlstatementdeclaration' }], + ], + + controlstatementdeclaration: [ + ['(in|from|through|if|to)\\b', { token: 'keyword.flow.control.at-rule' }], + { include: '@term' }, + ['{', { token: 'punctuation.curly', bracket: '@open', switchTo: '@selectorbody' }], + ], + + functionbody: [ + ['[@](return)', { token: TOKEN_AT_KEYWORD }], + { include: '@variabledeclaration' }, + { include: '@term' }, + { include: '@controlstatement' }, + [';', 'punctuation'], + ['}', { token: 'punctuation.curly', bracket: '@close', next: '@pop' }], + ], + + functioninvocation: [ + ['@identifier\\(', { token: 'support.function.name', bracket: '@open', next: '@functionarguments' }], + ], + + functionarguments: [ + ['\\$@identifier@ws:', TOKEN_PROPERTY], + ['[,]', 'punctuation'], + { include: '@term' }, + ['\\)', { token: 'support.function.name', bracket: '@close', next: '@pop' }], + ], + + strings: [ + ['~?"', { token: 'string.punctuation', bracket: '@open', next: '@stringenddoublequote' }], + ['~?\'', { token: 'string.punctuation', bracket: '@open', next: '@stringendquote' }] + ], + + stringenddoublequote: [ + ['\\\\.', 'string'], + ['"', { token: 'string.punctuation', next: '@pop', bracket: '@close' }], + ['.', 'string'] + ], + + stringendquote: [ + ['\\\\.', 'string'], + ['\'', { token: 'string.punctuation', next: '@pop', bracket: '@close' }], + ['.', 'string'] + ] + } +}; \ No newline at end of file