diff --git a/gulpfile.js b/gulpfile.js index d24f74c0..81220b7c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -73,7 +73,8 @@ gulp.task('release', ['clean-release','compile'], function() { bundleOne('src/sql'), bundleOne('src/swift'), bundleOne('src/vb'), - bundleOne('src/xml') + bundleOne('src/xml'), + bundleOne('src/yaml') ) .pipe(uglify({ preserveComments: 'some' diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index 539d79b7..318a204f 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -247,3 +247,10 @@ registerLanguage({ mimetypes: ['text/css'], module: './css' }); +registerLanguage({ + id: 'yaml', + extensions: ['.yaml', '.yml'], + aliases: ['YAML', 'yaml', 'YML', 'yml'], + mimetypes: ['application/x-yaml'], + module: './yaml' +}); diff --git a/src/yaml.ts b/src/yaml.ts new file mode 100644 index 00000000..49cbc6c5 --- /dev/null +++ b/src/yaml.ts @@ -0,0 +1,193 @@ +import IRichLanguageConfiguration = monaco.languages.LanguageConfiguration; +import ILanguage = monaco.languages.IMonarchLanguage; + +export const conf: IRichLanguageConfiguration = { + comments: { + lineComment: '#' + }, + brackets: [['{', '}'], ['[', ']']], + autoClosingPairs: [ + { open: '"', close: '"', notIn: ['string', 'comment'] }, + { open: '\'', close: '\'', notIn: ['string', 'comment'] }, + { open: '{', close: '}', notIn: ['string', 'comment'] }, + { open: '[', close: ']', notIn: ['string', 'comment'] } + ] +}; + +export const language = { + tokenPostfix: '.yaml', + + brackets: [ + { token: 'delimiter.bracket', open: '{', close: '}' }, + { token: 'delimiter.square', open: '[', close: ']' } + ], + + keywords: ['true', 'True', 'TRUE', 'false', 'False', 'FALSE', 'null', 'Null', 'Null', '~'], + + numberInteger: /(?:0|[+-]?[0-9]+)/, + numberFloat: /(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/, + numberOctal: /0o[0-7]+/, + numberHex: /0x[0-9a-fA-F]+/, + numberInfinity: /[+-]?\.(?:inf|Inf|INF)/, + numberNaN: /\.(?:nan|Nan|NAN)/, + numberDate: /\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/, + + escapes: /\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/, + + tokenizer: { + root: [ + {include: '@whitespace'}, + {include: '@comment'}, + + // Directive + [/%[^ ]+.*$/, 'meta.directive'], + + // Document Markers + [/---/, 'operators.directivesEnd'], + [/\.{3}/, 'operators.documentEnd'], + + // Block Structure Indicators + [/[-?:](?= )/, 'operators'], + + {include: '@anchor'}, + {include: '@tagHandle'}, + {include: '@flowCollections'}, + {include: '@blockStyle'}, + + // Numbers + [/@numberInteger(?![ \t]*\S+)/, 'number'], + [/@numberFloat(?![ \t]*\S+)/, 'number.float'], + [/@numberOctal(?![ \t]*\S+)/, 'number.octal'], + [/@numberHex(?![ \t]*\S+)/, 'number.hex'], + [/@numberInfinity(?![ \t]*\S+)/, 'number.infinity'], + [/@numberNaN(?![ \t]*\S+)/, 'number.nan'], + [/@numberDate(?![ \t]*\S+)/, 'number.date'], + + // Key:Value pair + [/(".*?"|'.*?'|.*?)([ \t]*)(:)( |$)/, ['type', 'white', 'operators', 'white']], + + {include: '@flowScalars'}, + + // String nodes + [/.+$/, {cases: {'@keywords': 'keyword', '@default': 'string'}}] + ], + + // Flow Collection: Flow Mapping + object: [ + {include: '@whitespace'}, + {include: '@comment'}, + + // Flow Mapping termination + [/\}/, '@brackets', '@pop'], + + // Flow Mapping delimiter + [/,/, 'delimiter.comma'], + + // Flow Mapping Key:Value delimiter + [/:(?= )/, 'operators'], + + // Flow Mapping Key:Value key + [/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/, 'type'], + + // Start Flow Style + {include: '@flowCollections'}, + {include: '@flowScalars'}, + + // Scalar Data types + {include: '@tagHandle'}, + {include: '@anchor'}, + {include: '@flowNumber'}, + + // Other value (keyword or string) + [/[^\},]+/, {cases: {'@keywords': 'keyword', '@default': 'string'}}] + ], + + // Flow Collection: Flow Sequence + array: [ + {include: '@whitespace'}, + {include: '@comment'}, + + // Flow Sequence termination + [/\]/, '@brackets', '@pop'], + + // Flow Sequence delimiter + [/,/, 'delimiter.comma'], + + // Start Flow Style + {include: '@flowCollections'}, + {include: '@flowScalars'}, + + // Scalar Data types + {include: '@tagHandle'}, + {include: '@anchor'}, + {include: '@flowNumber'}, + + // Other value (keyword or string) + [/[^\],]+/, {cases: {'@keywords': 'keyword', '@default': 'string'}}] + ], + + // Flow Scalars (quoted strings) + string: [ + [/[^\\"']+/, 'string'], + [/@escapes/, 'string.escape'], + [/\\./, 'string.escape.invalid'], + [/["']/, {cases: {'$#==$S2': {token: 'string', next: '@pop'}, '@default': 'string'}}] + ], + + // First line of a Block Style + multiString: [ + [/^( +).+$/, 'string', '@multiStringContinued.$1'] + ], + + // Further lines of a Block Style + // Workaround for indentation detection + multiStringContinued: [ + [/^( *).+$/, {cases: {'$1==$S2': 'string', '@default': {token: '@rematch', next: '@popall'}}}] + ], + + whitespace: [ + [/[ \t\r\n]+/, 'white'] + ], + + // Only line comments + comment: [ + [/#.*$/, 'comment'] + ], + + // Start Flow Collections + flowCollections: [ + [/\[/, '@brackets', '@array'], + [/\{/, '@brackets', '@object'] + ], + + // Start Flow Scalars (quoted strings) + flowScalars: [ + [/"/, 'string', '@string."'], + [/'/, 'string', '@string.\''] + ], + + // Start Block Scalar + blockStyle: [ + [/[>|][0-9]*[+-]?$/, 'operators', '@multiString'] + ], + + // Numbers in Flow Collections (terminate with ,]}) + flowNumber: [ + [/@numberInteger(?=[ \t]*[,\]\}])/, 'number'], + [/@numberFloat(?=[ \t]*[,\]\}])/, 'number.float'], + [/@numberOctal(?=[ \t]*[,\]\}])/, 'number.octal'], + [/@numberHex(?=[ \t]*[,\]\}])/, 'number.hex'], + [/@numberInfinity(?=[ \t]*[,\]\}])/, 'number.infinity'], + [/@numberNaN(?=[ \t]*[,\]\}])/, 'number.nan'], + [/@numberDate(?=[ \t]*[,\]\}])/, 'number.date'] + ], + + tagHandle: [ + [/\![^ ]*/, 'tag'] + ], + + anchor: [ + [/[&*][^ ]+/, 'namespace'] + ] + } +}; diff --git a/test/all.js b/test/all.js index f53caea0..264734ed 100644 --- a/test/all.js +++ b/test/all.js @@ -48,6 +48,7 @@ requirejs([ 'out/test/sql.test', 'out/test/vb.test', 'out/test/xml.test', + 'out/test/yaml.test' ], function() { run(); // We can launch the tests! }); diff --git a/test/yaml.test.ts b/test/yaml.test.ts new file mode 100644 index 00000000..00006989 --- /dev/null +++ b/test/yaml.test.ts @@ -0,0 +1,288 @@ +import {testTokenization} from './testRunner'; + +testTokenization('yaml', [ + // YAML directive + [{ + line: '%YAML 1.2', + tokens: [{ + startIndex: 0, + type: 'meta.directive.yaml' + }] + }], + + // Comments + [{ + line: '#Comment', + tokens: [{ + startIndex: 0, + type: 'comment.yaml' + }] + }], + + // Document Marker - Directives End + [{ + line: '---', + tokens: [{ + startIndex: 0, + type: 'operators.directivesEnd.yaml' + }] + }], + + // Document Marker - Document End + [{ + line: '...', + tokens: [{ + startIndex: 0, + type: 'operators.documentEnd.yaml' + }] + }], + + // Tag Handle + [{ + line: '!', + tokens: [{ + startIndex: 0, + type: 'tag.yaml' + }] + }], + + // Key: + [{ + line: 'key:', + tokens: [{ + startIndex: 0, + type: 'type.yaml' + }, { + startIndex: 3, + type: 'operators.yaml' + }] + }], + + // Key:Value + [{ + line: 'key: value', + tokens: [{ + startIndex: 0, + type: 'type.yaml' + }, { + startIndex: 3, + type: 'operators.yaml' + }, { + startIndex: 4, + type: 'white.yaml' + }, { + startIndex: 5, + type: 'string.yaml' + }] + }], + + // Key:Value - Quoted Keys + [{ + line: '":": value', + tokens: [{ + startIndex: 0, + type: 'type.yaml' + }, { + startIndex: 3, + type: 'operators.yaml' + }, { + startIndex: 4, + type: 'white.yaml' + }, { + startIndex: 5, + type: 'string.yaml' + }] + }], + + // Tag Handles + [{ + line: '!!str string', + tokens: [{ + startIndex: 0, + type: 'tag.yaml' + }, { + startIndex: 5, + type: 'white.yaml' + }, { + startIndex: 6, + type: 'string.yaml' + }] + }], + + // Anchor + [{ + line: 'anchor: &anchor', + tokens: [{ + startIndex: 0, + type: 'type.yaml' + }, { + startIndex: 6, + type: 'operators.yaml' + }, { + startIndex: 7, + type: 'white.yaml' + }, { + startIndex: 8, + type: 'namespace.yaml' + }] + }], + + // Alias + [{ + line: 'alias: *alias', + tokens: [{ + startIndex: 0, + type: 'type.yaml' + }, { + startIndex: 5, + type: 'operators.yaml' + }, { + startIndex: 6, + type: 'white.yaml' + }, { + startIndex: 7, + type: 'namespace.yaml' + }] + }], + + // Block Scalar + [{ + line: '>', + tokens: [{ + startIndex: 0, + type: 'operators.yaml' + }] + }, { + line: ' String', + tokens: [{ + startIndex: 0, + type: 'string.yaml' + }] + }], + + // Block Structure + [{ + line: '- one', + tokens: [{ + startIndex: 0, + type: 'operators.yaml' + }, { + startIndex: 1, + type: 'white.yaml' + }, { + startIndex: 2, + type: 'string.yaml' + }] + }, { + line: '? two', + tokens: [{ + startIndex: 0, + type: 'operators.yaml' + }, { + startIndex: 1, + type: 'white.yaml' + }, { + startIndex: 2, + type: 'string.yaml' + }] + }, { + line: ': three', + tokens: [{ + startIndex: 0, + type: 'operators.yaml' + }, { + startIndex: 1, + type: 'white.yaml' + }, { + startIndex: 2, + type: 'string.yaml' + }] + }], + + // Flow Mapping + [{ + line: '{key: value, number: 123}', + tokens: [{ + startIndex: 0, + type: 'delimiter.bracket.yaml' + }, { + startIndex: 1, + type: 'type.yaml' + }, { + startIndex: 4, + type: 'operators.yaml' + }, { + startIndex: 5, + type: 'white.yaml' + }, { + startIndex: 6, + type: 'string.yaml' + }, { + startIndex: 11, + type: 'delimiter.comma.yaml' + }, { + startIndex: 12, + type: 'white.yaml' + }, { + startIndex: 13, + type: 'type.yaml' + }, { + startIndex: 19, + type: 'operators.yaml' + }, { + startIndex: 20, + type: 'white.yaml' + }, { + startIndex: 21, + type: 'number.yaml' + }, { + startIndex: 24, + type: 'delimiter.bracket.yaml' + }, ] + }], + + // Flow Sequence - Data types + [{ + line: '[string,"double",\'single\',1,1.1,2002-04-28]', + tokens: [{ + startIndex: 0, + type: 'delimiter.square.yaml' + }, { + startIndex: 1, + type: 'string.yaml' + }, { + startIndex: 7, + type: 'delimiter.comma.yaml' + }, { + startIndex: 8, + type: 'string.yaml' + }, { + startIndex: 16, + type: 'delimiter.comma.yaml' + }, { + startIndex: 17, + type: 'string.yaml' + }, { + startIndex: 25, + type: 'delimiter.comma.yaml' + }, { + startIndex: 26, + type: 'number.yaml' + }, { + startIndex: 27, + type: 'delimiter.comma.yaml' + }, { + startIndex: 28, + type: 'number.float.yaml' + }, { + startIndex: 31, + type: 'delimiter.comma.yaml' + }, { + startIndex: 32, + type: 'number.date.yaml' + }, { + startIndex: 42, + type: 'delimiter.square.yaml' + }] + }] +]); diff --git a/tsconfig.json b/tsconfig.json index c3dd46ec..d3eaa29e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -40,6 +40,7 @@ "src/swift.ts", "src/vb.ts", "src/xml.ts", + "test/yaml.ts", "test/assert.d.ts", "test/bat.test.ts", "test/coffee.test.ts", @@ -68,6 +69,7 @@ "test/testRunner.ts", "test/vb.test.ts", "test/xml.test.ts", + "test/yaml.test.ts", "node_modules/monaco-editor-core/monaco.d.ts" ] }