From f0c6ccccce47ead5060f22a29faef3845742bd95 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 29 Feb 2016 22:29:09 +0100 Subject: [PATCH] Fixes #3494: [snippets] [debt] don't allow snippet syntax in default values --- .../server/src/json-toolbox/jsonSchema.ts | 67 ++++++++++--------- extensions/json/server/src/jsonCompletion.ts | 52 ++++++++++---- extensions/json/server/src/jsonParser.ts | 10 +-- .../json/server/src/test/completion.test.ts | 46 +++++++++++-- src/vs/base/common/jsonSchema.ts | 66 +++++++++--------- .../editor/common/services/modeServiceImpl.ts | 6 +- .../editor/contrib/snippet/common/snippet.ts | 5 -- src/vs/editor/node/textMate/TMSnippets.ts | 4 +- src/vs/editor/node/textMate/TMSyntax.ts | 4 +- .../json/common/json.contribution.ts | 2 +- .../languages/json/common/jsonIntellisense.ts | 56 ++++++++++++---- .../json/common/parser/jsonParser.ts | 14 ++-- .../common/configurationRegistry.ts | 4 +- src/vs/platform/configuration/common/model.ts | 5 +- .../common/jsonValidationExtensionPoint.ts | 4 +- .../debug/node/debugConfigurationManager.ts | 6 +- .../electron-browser/snippets.contribution.ts | 5 +- .../electron-browser/keybindingService.ts | 2 +- .../services/themes/node/themeService.ts | 4 +- 19 files changed, 227 insertions(+), 135 deletions(-) diff --git a/extensions/json/server/src/json-toolbox/jsonSchema.ts b/extensions/json/server/src/json-toolbox/jsonSchema.ts index 25d2902f4d1..9b892d1cad8 100644 --- a/extensions/json/server/src/json-toolbox/jsonSchema.ts +++ b/extensions/json/server/src/json-toolbox/jsonSchema.ts @@ -5,42 +5,43 @@ 'use strict'; export interface IJSONSchema { - id?:string; + id?: string; $schema?: string; - type?:any; - title?:string; - default?:any; - definitions?:IJSONSchemaMap; - description?:string; + type?: string | string[]; + title?: string; + default?: any; + definitions?: IJSONSchemaMap; + description?: string; properties?: IJSONSchemaMap; - patternProperties?:IJSONSchemaMap; - additionalProperties?:any; - minProperties?:number; - maxProperties?:number; - dependencies?:any; - items?:any; - minItems?:number; - maxItems?:number; - uniqueItems?:boolean; - additionalItems?:boolean; - pattern?:string; - minLength?:number; - maxLength?:number; - minimum?:number; - maximum?:number; - exclusiveMinimum?:boolean; - exclusiveMaximum?:boolean; - multipleOf?:number; - required?:string[]; - $ref?:string; - anyOf?:IJSONSchema[]; - allOf?:IJSONSchema[]; - oneOf?:IJSONSchema[]; - not?:IJSONSchema; - enum?:any[]; + patternProperties?: IJSONSchemaMap; + additionalProperties?: boolean | IJSONSchema; + minProperties?: number; + maxProperties?: number; + dependencies?: IJSONSchemaMap | string[]; + items?: IJSONSchema | IJSONSchema[]; + minItems?: number; + maxItems?: number; + uniqueItems?: boolean; + additionalItems?: boolean; + pattern?: string; + minLength?: number; + maxLength?: number; + minimum?: number; + maximum?: number; + exclusiveMinimum?: boolean; + exclusiveMaximum?: boolean; + multipleOf?: number; + required?: string[]; + $ref?: string; + anyOf?: IJSONSchema[]; + allOf?: IJSONSchema[]; + oneOf?: IJSONSchema[]; + not?: IJSONSchema; + enum?: any[]; format?: string; - - errorMessage?:string; // VS code internal + + defaultSnippets?: { label?: string; description?: string; body: any; }[]; // VSCode extension + errorMessage?: string; // VSCode extension } export interface IJSONSchemaMap { diff --git a/extensions/json/server/src/jsonCompletion.ts b/extensions/json/server/src/jsonCompletion.ts index 9cfcc0d53b1..a8d524e2516 100644 --- a/extensions/json/server/src/jsonCompletion.ts +++ b/extensions/json/server/src/jsonCompletion.ts @@ -172,7 +172,7 @@ export class JSONCompletion { if (schemaProperties) { Object.keys(schemaProperties).forEach((key: string) => { let propertySchema = schemaProperties[key]; - collector.add({ kind: CompletionItemKind.Property, label: key, insertText: this.getSnippetForProperty(key, propertySchema, addValue, isLast), documentation: propertySchema.description || '' }); + collector.add({ kind: CompletionItemKind.Property, label: key, insertText: this.getTextForProperty(key, propertySchema, addValue, isLast), documentation: propertySchema.description || '' }); }); } } @@ -183,7 +183,7 @@ export class JSONCompletion { let collectSuggestionsForSimilarObject = (obj: Parser.ObjectASTNode) => { obj.properties.forEach((p) => { let key = p.key.value; - collector.add({ kind: CompletionItemKind.Property, label: key, insertText: this.getSnippetForSimilarProperty(key, p.value), documentation: '' }); + collector.add({ kind: CompletionItemKind.Property, label: key, insertText: this.getTextForSimilarProperty(key, p.value), documentation: '' }); }); }; if (node.parent) { @@ -206,14 +206,14 @@ export class JSONCompletion { } } if (!currentKey && currentWord.length > 0) { - collector.add({ kind: CompletionItemKind.Property, label: JSON.stringify(currentWord), insertText: this.getSnippetForProperty(currentWord, null, true, isLast), documentation: '' }); + collector.add({ kind: CompletionItemKind.Property, label: this.getLabelForValue(currentWord), insertText: this.getTextForProperty(currentWord, null, true, isLast), documentation: '' }); } } private getSchemaLessValueSuggestions(doc: Parser.JSONDocument, node: Parser.ASTNode, offset: number, document: ITextDocument, collector: ISuggestionsCollector): void { let collectSuggestionsForValues = (value: Parser.ASTNode) => { if (!value.contains(offset)) { - let content = this.getMatchingSnippet(value, document); + let content = this.getTextForMatchingNode(value, document); collector.add({ kind: this.getSuggestionKind(value.type), label: content, insertText: content, documentation: '' }); } if (value.type === 'boolean') { @@ -347,6 +347,16 @@ export class JSONCompletion { detail: nls.localize('json.suggest.default', 'Default value'), }); } + if (Array.isArray(schema.defaultSnippets)) { + schema.defaultSnippets.forEach(s => { + collector.add({ + kind: CompletionItemKind.Snippet, + label: this.getLabelForSnippetValue(s.body), + insertText: this.getTextForSnippetValue(s.body) + }); + }); + } + if (Array.isArray(schema.allOf)) { schema.allOf.forEach((s) => this.addDefaultSuggestion(s, collector)); } @@ -360,7 +370,15 @@ export class JSONCompletion { private getLabelForValue(value: any): string { let label = JSON.stringify(value); - label = label.replace('{{', '').replace('}}', ''); + if (label.length > 57) { + return label.substr(0, 57).trim() + '...'; + } + return label; + } + + private getLabelForSnippetValue(value: any): string { + let label = JSON.stringify(value); + label = label.replace(/\{\{|\}\}/g, ''); if (label.length > 57) { return label.substr(0, 57).trim() + '...'; } @@ -368,11 +386,17 @@ export class JSONCompletion { } private getTextForValue(value: any): string { + var text = JSON.stringify(value, null, '\t'); + text = text.replace(/[\\\{\}]/g, '\\$&'); + return text; + } + + private getTextForSnippetValue(value: any): string { return JSON.stringify(value, null, '\t'); } - private getSnippetForValue(value: any): string { - let snippet = JSON.stringify(value, null, '\t'); + private getTextForEnumValue(value: any): string { + let snippet = this.getTextForValue(value); switch (typeof value) { case 'object': if (value === null) { @@ -405,7 +429,7 @@ export class JSONCompletion { } - private getMatchingSnippet(node: Parser.ASTNode, document: ITextDocument): string { + private getTextForMatchingNode(node: Parser.ASTNode, document: ITextDocument): string { switch (node.type) { case 'array': return '[]'; @@ -417,9 +441,9 @@ export class JSONCompletion { } } - private getSnippetForProperty(key: string, propertySchema: JsonSchema.IJSONSchema, addValue: boolean, isLast: boolean): string { + private getTextForProperty(key: string, propertySchema: JsonSchema.IJSONSchema, addValue: boolean, isLast: boolean): string { - let result = '"' + key + '"'; + let result = this.getTextForValue(key); if (!addValue) { return result; } @@ -428,9 +452,9 @@ export class JSONCompletion { if (propertySchema) { let defaultVal = propertySchema.default; if (typeof defaultVal !== 'undefined') { - result = result + this.getSnippetForValue(defaultVal); + result = result + this.getTextForEnumValue(defaultVal); } else if (propertySchema.enum && propertySchema.enum.length > 0) { - result = result + this.getSnippetForValue(propertySchema.enum[0]); + result = result + this.getTextForEnumValue(propertySchema.enum[0]); } else { var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type; switch (type) { @@ -465,8 +489,8 @@ export class JSONCompletion { return result; } - private getSnippetForSimilarProperty(key: string, templateValue: Parser.ASTNode): string { - return '"' + key + '"'; + private getTextForSimilarProperty(key: string, templateValue: Parser.ASTNode): string { + return this.getTextForValue(key); } private getCurrentWord(document: ITextDocument, offset: number) { diff --git a/extensions/json/server/src/jsonParser.ts b/extensions/json/server/src/jsonParser.ts index 810b7d51b0f..2e33d41058c 100644 --- a/extensions/json/server/src/jsonParser.ts +++ b/extensions/json/server/src/jsonParser.ts @@ -103,7 +103,7 @@ export class ASTNode { if ((schema.type).indexOf(this.type) === -1) { validationResult.warnings.push({ location: { start: this.start, end: this.end }, - message: nls.localize('typeArrayMismatchWarning', 'Incorrect type. Expected one of {0}', schema.type.join(', ')) + message: nls.localize('typeArrayMismatchWarning', 'Incorrect type. Expected one of {0}', (schema.type).join(', ')) }); } } @@ -277,14 +277,14 @@ export class ArrayASTNode extends ASTNode { super.validate(schema, validationResult, matchingSchemas, offset); if (Array.isArray(schema.items)) { - let subSchemas: JsonSchema.IJSONSchema[] = schema.items; + let subSchemas = schema.items; subSchemas.forEach((subSchema, index) => { let itemValidationResult = new ValidationResult(); let item = this.items[index]; if (item) { item.validate(subSchema, itemValidationResult, matchingSchemas, offset); validationResult.mergePropertyMatch(itemValidationResult); - } else if (this.items.length >= schema.items.length) { + } else if (this.items.length >= subSchemas.length) { validationResult.propertiesValueMatches++; } }); @@ -294,8 +294,8 @@ export class ArrayASTNode extends ASTNode { location: { start: this.start, end: this.end }, message: nls.localize('additionalItemsWarning', 'Array has too many items according to schema. Expected {0} or fewer', subSchemas.length) }); - } else if (this.items.length >= schema.items.length) { - validationResult.propertiesValueMatches += (this.items.length - schema.items.length); + } else if (this.items.length >= subSchemas.length) { + validationResult.propertiesValueMatches += (this.items.length - subSchemas.length); } } else if (schema.items) { diff --git a/extensions/json/server/src/test/completion.test.ts b/extensions/json/server/src/test/completion.test.ts index 1741d08f165..3872cc36294 100644 --- a/extensions/json/server/src/test/completion.test.ts +++ b/extensions/json/server/src/test/completion.test.ts @@ -24,7 +24,7 @@ suite('JSON Completion', () => { var matches = completions.filter(function(completion: CompletionItem) { return completion.label === label && (!documentation || completion.documentation === documentation); }); - assert.equal(matches.length, 1, label + " should only existing once"); + assert.equal(matches.length, 1, label + " should only existing once: Actual: " + completions.map(c => c.label).join(', ')); if (document && resultText) { assert.equal(applyEdits(document, [ matches[0].textEdit ]), resultText); } @@ -51,8 +51,6 @@ suite('JSON Completion', () => { }) }; - - test('Complete keys no schema', function(testDone) { Promise.all([ testSuggestionsFor('[ { "name": "John", "age": 44 }, { /**/ }', '/**/', null, result => { @@ -478,4 +476,44 @@ suite('JSON Completion', () => { }), ]).then(() => testDone(), (error) => testDone(error)); }); -}); \ No newline at end of file + + test('Escaping no schema', function(testDone) { + Promise.all([ + testSuggestionsFor('[ { "\\\\{{}}": "John" }, { "/**/" }', '/**/', null, result => { + assertSuggestion(result, '\\{{}}'); + }), + testSuggestionsFor('[ { "\\\\{{}}": "John" }, { /**/ }', '/**/', null, (result, document) => { + assertSuggestion(result, '\\{{}}', null, document, '[ { "\\\\{{}}": "John" }, { "\\\\\\\\\\{\\{\\}\\}"/**/ }'); + }), + testSuggestionsFor('[ { "name": "\\{" }, { "name": /**/ }', '/**/', null, result => { + assertSuggestion(result, '"\\{"'); + }) + ]).then(() => testDone(), (error) => testDone(error)); + }); + + test('Escaping with schema', function(testDone) { + var schema: JsonSchema.IJSONSchema = { + type: 'object', + properties: { + '{\\}': { + default: "{\\}", + defaultSnippets: [ { body: "{{var}}"} ], + enum: ['John{\\}'] + } + } + }; + + Promise.all([ + testSuggestionsFor('{ /**/ }', '/**/', schema, (result, document) => { + assertSuggestion(result, '{\\}', null, document, '{ "\\{\\\\\\\\\\}": "{{\\{\\\\\\\\\\}}}"/**/ }'); + }), + testSuggestionsFor('{ "{\\\\}": /**/ }', '/**/', schema, (result, document) => { + assertSuggestion(result, '"{\\\\}"', null, document, '{ "{\\\\}": "\\{\\\\\\\\\\}"/**/ }'); + assertSuggestion(result, '"John{\\\\}"', null, document, '{ "{\\\\}": "John\\{\\\\\\\\\\}"/**/ }'); + assertSuggestion(result, '"var"', null, document, '{ "{\\\\}": "{{var}}"/**/ }'); + }) + ]).then(() => testDone(), (error) => testDone(error)); + }); + +}); + diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index 3966c60f145..9b892d1cad8 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -5,41 +5,43 @@ 'use strict'; export interface IJSONSchema { - id?:string; + id?: string; $schema?: string; - type?:any; - title?:string; - default?:any; - definitions?:IJSONSchemaMap; - description?:string; + type?: string | string[]; + title?: string; + default?: any; + definitions?: IJSONSchemaMap; + description?: string; properties?: IJSONSchemaMap; - patternProperties?:IJSONSchemaMap; - additionalProperties?:any; - minProperties?:number; - maxProperties?:number; - dependencies?:any; - items?:any; - minItems?:number; - maxItems?:number; - uniqueItems?:boolean; - additionalItems?:boolean; - pattern?:string; - errorMessage?: string; - minLength?:number; - maxLength?:number; - minimum?:number; - maximum?:number; - exclusiveMinimum?:boolean; - exclusiveMaximum?:boolean; - multipleOf?:number; - required?:string[]; - $ref?:string; - anyOf?:IJSONSchema[]; - allOf?:IJSONSchema[]; - oneOf?:IJSONSchema[]; - not?:IJSONSchema; - enum?:any[]; + patternProperties?: IJSONSchemaMap; + additionalProperties?: boolean | IJSONSchema; + minProperties?: number; + maxProperties?: number; + dependencies?: IJSONSchemaMap | string[]; + items?: IJSONSchema | IJSONSchema[]; + minItems?: number; + maxItems?: number; + uniqueItems?: boolean; + additionalItems?: boolean; + pattern?: string; + minLength?: number; + maxLength?: number; + minimum?: number; + maximum?: number; + exclusiveMinimum?: boolean; + exclusiveMaximum?: boolean; + multipleOf?: number; + required?: string[]; + $ref?: string; + anyOf?: IJSONSchema[]; + allOf?: IJSONSchema[]; + oneOf?: IJSONSchema[]; + not?: IJSONSchema; + enum?: any[]; format?: string; + + defaultSnippets?: { label?: string; description?: string; body: any; }[]; // VSCode extension + errorMessage?: string; // VSCode extension } export interface IJSONSchemaMap { diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index d16b2afe628..ef2d5091c2c 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -38,10 +38,10 @@ interface IModeConfigurationMap { [modeId: string]: any; } let languagesExtPoint = PluginsRegistry.registerExtensionPoint('languages', { description: nls.localize('vscode.extension.contributes.languages', 'Contributes language declarations.'), type: 'array', - default: [{ id: '', aliases: [], extensions: [] }], + defaultSnippets: [{ body: [{ id: '', aliases: [], extensions: [] }] }], items: { type: 'object', - default: { id: '', extensions: [] }, + defaultSnippets: [{ body: { id: '', extensions: [] } }], properties: { id: { description: nls.localize('vscode.extension.contributes.languages.id', 'ID of the language.'), @@ -73,7 +73,7 @@ let languagesExtPoint = PluginsRegistry.registerExtensionPoint/?"') { - this.lines.push(template); - return; - } - var placeHoldersMap: collections.IStringDictionary = {}; var i: number, len: number, j: number, lenJ: number, templateLines = template.split('\n'); diff --git a/src/vs/editor/node/textMate/TMSnippets.ts b/src/vs/editor/node/textMate/TMSnippets.ts index 1b6d7510b92..48d39c8710e 100644 --- a/src/vs/editor/node/textMate/TMSnippets.ts +++ b/src/vs/editor/node/textMate/TMSnippets.ts @@ -33,10 +33,10 @@ export function snippetUpdated(modeId: string, filePath: string): TPromise let snippetsExtensionPoint = PluginsRegistry.registerExtensionPoint('snippets', { description: nls.localize('vscode.extension.contributes.snippets', 'Contributes textmate snippets.'), type: 'array', - default: [{ language: '', path: '' }], + defaultSnippets: [ { body: [{ language: '', path: '' }] }], items: { type: 'object', - default: { language: '{{id}}', path: './snippets/{{id}}.json.'}, + defaultSnippets: [ { body: { language: '{{id}}', path: './snippets/{{id}}.json.'} }] , properties: { language: { description: nls.localize('vscode.extension.contributes.snippets-language', 'Language id for which this snippet is contributed to.'), diff --git a/src/vs/editor/node/textMate/TMSyntax.ts b/src/vs/editor/node/textMate/TMSyntax.ts index 00e6c441759..387f9d031b7 100644 --- a/src/vs/editor/node/textMate/TMSyntax.ts +++ b/src/vs/editor/node/textMate/TMSyntax.ts @@ -23,10 +23,10 @@ export interface ITMSyntaxExtensionPoint { let grammarsExtPoint = PluginsRegistry.registerExtensionPoint('grammars', { description: nls.localize('vscode.extension.contributes.grammars', 'Contributes textmate tokenizers.'), type: 'array', - default: [{ id: '', extensions: [] }], + defaultSnippets: [ { body: [{ id: '', extensions: [] }] }], items: { type: 'object', - default: { language: '{{id}}', scopeName: 'source.{{id}}', path: './syntaxes/{{id}}.tmLanguage.'}, + defaultSnippets: [ { body: { language: '{{id}}', scopeName: 'source.{{id}}', path: './syntaxes/{{id}}.tmLanguage.'} }], properties: { language: { description: nls.localize('vscode.extension.contributes.grammars.language', 'Language id for which this syntax is contributed to.'), diff --git a/src/vs/languages/json/common/json.contribution.ts b/src/vs/languages/json/common/json.contribution.ts index d80055754b0..779254651c5 100644 --- a/src/vs/languages/json/common/json.contribution.ts +++ b/src/vs/languages/json/common/json.contribution.ts @@ -30,7 +30,7 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('jsonConfiguration.schemas', "Associate schemas to JSON files in the current project"), 'items': { 'type': 'object', - 'default': { fileMatch: [ '{{/myfile}}' ], url: '{{schemaURL}}' }, + 'defaultSnippets': [{ body: { fileMatch: [ '{{/myfile}}' ], url: '{{schemaURL}}' } }], 'properties': { 'url': { 'type': 'string', diff --git a/src/vs/languages/json/common/jsonIntellisense.ts b/src/vs/languages/json/common/jsonIntellisense.ts index 51bee2c2b46..085bf3fedc3 100644 --- a/src/vs/languages/json/common/jsonIntellisense.ts +++ b/src/vs/languages/json/common/jsonIntellisense.ts @@ -179,7 +179,7 @@ export class JSONIntellisense { if (schemaProperties) { Object.keys(schemaProperties).forEach((key: string) => { var propertySchema = schemaProperties[key]; - collector.add({ type: 'property', label: key, codeSnippet: this.getSnippetForProperty(key, propertySchema, addValue, isLast), documentationLabel: propertySchema.description || '' }); + collector.add({ type: 'property', label: key, codeSnippet: this.getTextForProperty(key, propertySchema, addValue, isLast), documentationLabel: propertySchema.description || '' }); }); } } @@ -190,7 +190,7 @@ export class JSONIntellisense { var collectSuggestionsForSimilarObject = (obj: Parser.ObjectASTNode) => { obj.properties.forEach((p) => { var key = p.key.value; - collector.add({ type: 'property', label: key, codeSnippet: this.getSnippetForSimilarProperty(key, p.value), documentationLabel: '' }); + collector.add({ type: 'property', label: key, codeSnippet: this.getTextForSimilarProperty(key, p.value), documentationLabel: '' }); }); }; if (node.parent.type === 'property') { @@ -214,7 +214,7 @@ export class JSONIntellisense { public getSchemaLessValueSuggestions(doc: Parser.JSONDocument, node: Parser.ASTNode, offset: number, modelMirror: EditorCommon.IMirrorModel, collector: JsonWorker.ISuggestionsCollector): void { var collectSuggestionsForValues = (value: Parser.ASTNode) => { - var content = this.getMatchingSnippet(value, modelMirror); + var content = this.getTextForMatchingNode(value, modelMirror); collector.add({ type: this.getSuggestionType(value.type), label: content, codeSnippet: content, documentationLabel: '' }); if (value.type === 'boolean') { this.addBooleanSuggestion(!value.getValue(), collector); @@ -301,12 +301,12 @@ export class JSONIntellisense { } private addBooleanSuggestion(value: boolean, collector: JsonWorker.ISuggestionsCollector): void { - collector.add({ type: this.getSuggestionType('boolean'), label: value ? 'true' : 'false', codeSnippet: this.getSnippetForValue(value), documentationLabel: '' }); + collector.add({ type: this.getSuggestionType('boolean'), label: value ? 'true' : 'false', codeSnippet: this.getTextForEnumValue(value), documentationLabel: '' }); } private addEnumSuggestion(schema: JsonSchema.IJSONSchema, collector: JsonWorker.ISuggestionsCollector): void { if (Array.isArray(schema.enum)) { - schema.enum.forEach((enm) => collector.add({ type: this.getSuggestionType(schema.type), label: this.getLabelForValue(enm), codeSnippet: this.getSnippetForValue(enm), documentationLabel: '' })); + schema.enum.forEach((enm) => collector.add({ type: this.getSuggestionType(schema.type), label: this.getLabelForValue(enm), codeSnippet: this.getTextForEnumValue(enm), documentationLabel: '' })); } else if (schema.type === 'boolean') { this.addBooleanSuggestion(true, collector); this.addBooleanSuggestion(false, collector); @@ -327,10 +327,19 @@ export class JSONIntellisense { collector.add({ type: this.getSuggestionType(schema.type), label: this.getLabelForValue(schema.default), - codeSnippet: this.getSnippetForValue(schema.default), + codeSnippet: this.getTextForValue(schema.default), typeLabel: nls.localize('json.suggest.default', 'Default value'), }); } + if (Array.isArray(schema.defaultSnippets)) { + schema.defaultSnippets.forEach(s => { + collector.add({ + type: 'snippet', + label: this.getLabelForSnippetValue(s.body), + codeSnippet: this.getTextForSnippetValue(s.body) + }); + }); + } if (Array.isArray(schema.allOf)) { schema.allOf.forEach((s) => this.addDefaultSuggestion(s, collector)); } @@ -351,7 +360,26 @@ export class JSONIntellisense { return label; } - private getSnippetForValue(value: any) : string { + private getLabelForSnippetValue(value: any): string { + let label = JSON.stringify(value); + label = label.replace(/\{\{|\}\}/g, ''); + if (label.length > 57) { + return label.substr(0, 57).trim() + '...'; + } + return label; + } + + private getTextForValue(value: any): string { + var text = JSON.stringify(value, null, '\t'); + text = text.replace(/[\\\{\}]/g, '\\$&'); + return text; + } + + private getTextForSnippetValue(value: any): string { + return JSON.stringify(value, null, '\t'); + } + + private getTextForEnumValue(value: any) : string { var snippet = JSON.stringify(value, null, '\t'); switch (typeof value) { case 'object': @@ -386,7 +414,7 @@ export class JSONIntellisense { } - private getMatchingSnippet(node: Parser.ASTNode, modelMirror: EditorCommon.IMirrorModel): string { + private getTextForMatchingNode(node: Parser.ASTNode, modelMirror: EditorCommon.IMirrorModel): string { switch (node.type) { case 'array': return '[]'; @@ -398,9 +426,9 @@ export class JSONIntellisense { } } - private getSnippetForProperty(key: string, propertySchema: JsonSchema.IJSONSchema, addValue:boolean, isLast: boolean): string { + private getTextForProperty(key: string, propertySchema: JsonSchema.IJSONSchema, addValue:boolean, isLast: boolean): string { - var result = '"' + key + '"'; + let result = this.getTextForValue(key); if (!addValue) { return result; } @@ -408,9 +436,9 @@ export class JSONIntellisense { var defaultVal = propertySchema.default; if (!Types.isUndefined(defaultVal)) { - result = result + this.getSnippetForValue(defaultVal); + result = result + this.getTextForEnumValue(defaultVal); } else if (propertySchema.enum && propertySchema.enum.length > 0) { - result = result + this.getSnippetForValue(propertySchema.enum[0]); + result = result + this.getTextForEnumValue(propertySchema.enum[0]); } else { switch (propertySchema.type) { case 'boolean': @@ -442,7 +470,7 @@ export class JSONIntellisense { return result; } - private getSnippetForSimilarProperty(key: string, templateValue: Parser.ASTNode): string { - return '"' + key + '"'; + private getTextForSimilarProperty(key: string, templateValue: Parser.ASTNode): string { + return this.getTextForValue(key); } } diff --git a/src/vs/languages/json/common/parser/jsonParser.ts b/src/vs/languages/json/common/parser/jsonParser.ts index ec6123c9cb8..b8a8b472107 100644 --- a/src/vs/languages/json/common/parser/jsonParser.ts +++ b/src/vs/languages/json/common/parser/jsonParser.ts @@ -102,10 +102,10 @@ export class ASTNode { } if (Array.isArray(schema.type)) { - if (Arrays.contains(schema.type, this.type) === false) { + if (Arrays.contains( schema.type, this.type) === false) { validationResult.warnings.push({ location: { start: this.start, end: this.end }, - message: nls.localize('typeArrayMismatchWarning', 'Incorrect type. Expected one of {0}', schema.type.join()) + message: nls.localize('typeArrayMismatchWarning', 'Incorrect type. Expected one of {0}', ( schema.type).join()) }); } } @@ -279,14 +279,14 @@ export class ArrayASTNode extends ASTNode { super.validate(schema, validationResult, matchingSchemas, offset); if (Array.isArray(schema.items)) { - var subSchemas:JsonSchema.IJSONSchema[] = schema.items; + var subSchemas = schema.items; subSchemas.forEach((subSchema, index) => { var itemValidationResult = new ValidationResult(); var item = this.items[index]; if (item) { item.validate(subSchema, itemValidationResult, matchingSchemas, offset); validationResult.mergePropertyMatch(itemValidationResult); - } else if (this.items.length >= schema.items.length) { + } else if (this.items.length >= subSchemas.length) { validationResult.propertiesValueMatches++; } }); @@ -296,8 +296,8 @@ export class ArrayASTNode extends ASTNode { location: { start: this.start, end: this.end }, message: nls.localize('additionalItemsWarning', 'Array has too many items according to schema. Expected {0} or fewer', subSchemas.length) }); - } else if (this.items.length >= schema.items.length) { - validationResult.propertiesValueMatches += (this.items.length - schema.items.length); + } else if (this.items.length >= subSchemas.length) { + validationResult.propertiesValueMatches += (this.items.length - subSchemas.length); } } else if (schema.items) { @@ -361,7 +361,7 @@ export class NumberASTNode extends ASTNode { // work around type validation in the base class var typeIsInteger = false; - if (schema.type === 'integer' || (Array.isArray(schema.type) && Arrays.contains(schema.type, 'integer'))) { + if (schema.type === 'integer' || (Array.isArray(schema.type) && Arrays.contains( schema.type, 'integer'))) { typeIsInteger = true; } if (typeIsInteger && this.isInteger === true) { diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 3074d0f8ac5..fef423eba29 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -39,7 +39,7 @@ export interface IConfigurationRegistry { export interface IConfigurationNode { id?: string; order?: number; - type?: string; + type?: string | string[]; title?: string; description?: string; default?: any; @@ -96,7 +96,7 @@ platform.Registry.add(Extensions.Configuration, configurationRegistry); let configurationExtPoint = PluginsRegistry.registerExtensionPoint('configuration', { description: nls.localize('vscode.extension.contributes.configuration', 'Contributes configuration settings.'), type: 'object', - default: { title: '', properties: {} }, + defaultSnippets: [{ body: { title: '', properties: {} } }], properties: { title: { description: nls.localize('vscode.extension.contributes.configuration.title', 'A summary of the settings. This label will be used in the settings file as separating comment.'), diff --git a/src/vs/platform/configuration/common/model.ts b/src/vs/platform/configuration/common/model.ts index eb458bb93ca..3af5a084e54 100644 --- a/src/vs/platform/configuration/common/model.ts +++ b/src/vs/platform/configuration/common/model.ts @@ -221,8 +221,9 @@ function addIndent(str: string): string { return str.split('\n').join('\n\t'); } -function getDefaultValue(type: string): any { - switch (type) { +function getDefaultValue(type: string | string[]): any { + let t = Array.isArray(type) ? ( type)[0] : type; + switch (t) { case 'boolean': return false; case 'integer': diff --git a/src/vs/platform/jsonschemas/common/jsonValidationExtensionPoint.ts b/src/vs/platform/jsonschemas/common/jsonValidationExtensionPoint.ts index 9a5d35825d7..29bae8ed9e2 100644 --- a/src/vs/platform/jsonschemas/common/jsonValidationExtensionPoint.ts +++ b/src/vs/platform/jsonschemas/common/jsonValidationExtensionPoint.ts @@ -23,10 +23,10 @@ let schemaRegistry = Registr let configurationExtPoint = PluginsRegistry.registerExtensionPoint('jsonValidation', { description: nls.localize('contributes.jsonValidation', 'Contributes json schema configuration.'), type: 'array', - default: [{ fileMatch: '{{file.json}}', url: '{{url}}' }], + defaultSnippets: [{ body: [{ fileMatch: '{{file.json}}', url: '{{url}}' }] }], items: { type: 'object', - default: { fileMatch: '{{file.json}}', url: '{{url}}' }, + defaultSnippets: [{ body: { fileMatch: '{{file.json}}', url: '{{url}}' } }], properties: { fileMatch: { type: 'string', diff --git a/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts index 590c9853718..8882f144069 100644 --- a/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts @@ -30,10 +30,10 @@ import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickO export var debuggersExtPoint = pluginsRegistry.PluginsRegistry.registerExtensionPoint('debuggers', { description: nls.localize('vscode.extension.contributes.debuggers', 'Contributes debug adapters.'), type: 'array', - default: [{ type: '', extensions: [] }], + defaultSnippets: [{ body: [{ type: '', extensions: [] }] }], items: { type: 'object', - default: { type: '', program: '', runtime: '', enableBreakpointsFor: { languageIds: [ '' ] } }, + defaultSnippets: [{ body: { type: '', program: '', runtime: '', enableBreakpointsFor: { languageIds: [ '' ] } } }], properties: { type: { description: nls.localize('vscode.extension.contributes.debuggers.type', "Unique identifier for this debug adapter."), @@ -205,7 +205,7 @@ export class ConfigurationManager { this.adapters.forEach(adapter => { const schemaAttributes = adapter.getSchemaAttributes(); if (schemaAttributes) { - schema.properties['configurations'].items.oneOf.push(...schemaAttributes); + ( schema.properties['configurations'].items).oneOf.push(...schemaAttributes); } }); }); diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts b/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts index 442a641ba07..8e819df9d36 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts @@ -132,7 +132,10 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenSn let schemaId = 'vscode://schemas/snippets'; let schema : IJSONSchema = { 'id': schemaId, - 'default': { '{{snippetName}}': { 'prefix': '{{prefix}}', 'body': '{{snippet}}', 'description': '{{description}}' } }, + 'defaultSnippets': [{ + 'label': nls.localize('snippetSchema.json.default', "Empty snippet"), + 'body': { '{{snippetName}}': { 'prefix': '{{prefix}}', 'body': '{{snippet}}', 'description': '{{description}}' } } + }], 'type': 'object', 'description': nls.localize('snippetSchema.json', 'User snippet configuration'), 'additionalProperties': { diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index 65286eb6faa..52c01033b89 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -292,7 +292,7 @@ let schema : IJSONSchema = { 'items': { 'required': ['key'], 'type': 'object', - 'default': { 'key': '{{_}}', 'command': '{{_}}', 'when': '{{_}}' }, + 'defaultSnippets': [ { 'body': { 'key': '{{_}}', 'command': '{{_}}', 'when': '{{_}}' } }], 'properties': { 'key': { 'type': 'string', diff --git a/src/vs/workbench/services/themes/node/themeService.ts b/src/vs/workbench/services/themes/node/themeService.ts index d35aec8e5e5..bd8794ff4e5 100644 --- a/src/vs/workbench/services/themes/node/themeService.ts +++ b/src/vs/workbench/services/themes/node/themeService.ts @@ -23,10 +23,10 @@ let defaultBaseTheme = Themes.getBaseThemeId(DEFAULT_THEME_ID); let themesExtPoint = PluginsRegistry.registerExtensionPoint('themes', { description: nls.localize('vscode.extension.contributes.themes', 'Contributes textmate color themes.'), type: 'array', - default: [{ label: '{{label}}', uiTheme: 'vs-dark', path: './themes/{{id}}.tmTheme.' }], + defaultSnippets: [{ body: [{ label: '{{label}}', uiTheme: 'vs-dark', path: './themes/{{id}}.tmTheme.' }] }], items: { type: 'object', - default: { label: '{{label}}', uiTheme: 'vs-dark', path: './themes/{{id}}.tmTheme.' }, + defaultSnippets: [{ body: { label: '{{label}}', uiTheme: 'vs-dark', path: './themes/{{id}}.tmTheme.' } }], properties: { label: { description: nls.localize('vscode.extension.contributes.themes.label', 'Label of the color theme as shown in the UI.'),