From 87b4d62ae24f54379604ad98da461c1a9d31b7bd Mon Sep 17 00:00:00 2001 From: Jiayin Pei Date: Tue, 4 Sep 2018 20:58:22 +0800 Subject: [PATCH 1/2] support generate snippets for object and array according to schema --- .../services/yamlCompletion.ts | 165 ++++++++++++++---- test/autoCompletion2.test.ts | 26 +-- test/autoCompletion3.test.ts | 2 +- 3 files changed, 147 insertions(+), 46 deletions(-) diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index 8fccc8a4..9025dac0 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -290,7 +290,16 @@ export class YAMLCompletion { if (index < s.schema.items.length) { this.addSchemaValueCompletions(s.schema.items[index], collector, separatorAfter, true); } - } else { + } else if (s.schema.items.type === 'object') { + collector.add({ + kind: this.getSuggestionKind(s.schema.items.type), + label: `- (array item)`, + documentation: `Create an item of an array${s.schema.description === undefined ? '' : '(' + s.schema.description + ')'}`, + insertText: `- ${this.getInsertTextForObject(s.schema.items, separatorAfter).insertText.trimLeft()}`, + insertTextFormat: InsertTextFormat.Snippet, + }); + } + else { this.addSchemaValueCompletions(s.schema.items, collector, separatorAfter, true); } } @@ -527,6 +536,96 @@ export class YAMLCompletion { return this.getInsertTextForPlainText(text + separatorAfter); } + private getInsertTextForObject(schema: JSONSchema, separatorAfter: string, indent = '\t', insertIndex = 1) { + let insertText = ""; + if (!schema.properties) { + insertText = `${indent}\$${insertIndex++}\n`; + return { insertText, insertIndex }; + } + + Object.keys(schema.properties).forEach((key: string) => { + let propertySchema = schema.properties[key]; + let type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type; + if (!type) { + if (propertySchema.properties) { + type = 'object'; + } + if (propertySchema.items) { + type = 'array'; + } + } + if (schema.required && schema.required.indexOf(key) > -1) { + switch (type) { + case 'boolean': + case 'string': + case 'number': + case 'integer': + insertText += `${indent}${key}: \$${insertIndex++}\n` + break; + case 'array': + let arrayInsertResult = this.getInsertTextForArray(propertySchema.items, separatorAfter, `${indent}\t`, insertIndex++); + insertIndex = arrayInsertResult.insertIndex; + insertText += `${indent}${key}:\n${indent}\t- ${arrayInsertResult.insertText}\n`; + break; + case 'object': + let objectInsertResult = this.getInsertTextForObject(propertySchema, separatorAfter, `${indent}\t`, insertIndex++); + insertIndex = objectInsertResult.insertIndex; + insertText += `${indent}${key}:\n${objectInsertResult.insertText}\n`; + break; + } + } else if (propertySchema.default !== undefined) { + switch (type) { + case 'boolean': + case 'string': + case 'number': + case 'integer': + insertText += `${indent}${key}: \${${insertIndex++}:${propertySchema.default}}\n` + break; + case 'array': + case 'object': + // TODO: support default value for array object + break; + } + } + }); + insertText = insertText.trimRight() + separatorAfter; + return { insertText, insertIndex }; + } + + private getInsertTextForArray(schema: JSONSchema, separatorAfter: string, indent = '\t', insertIndex = 1) { + let insertText = ''; + if (!schema) { + insertText = `\$${insertIndex++}`; + } + let type = Array.isArray(schema.type) ? schema.type[0] : schema.type; + if (!type) { + if (schema.properties) { + type = 'object'; + } + if (schema.items) { + type = 'array'; + } + } + switch (schema.type) { + case 'boolean': + insertText = `\${${insertIndex++}:false}`; + break; + case 'number': + case 'integer': + insertText = `\${${insertIndex++}:0}`; + break; + case 'string': + insertText = `\${${insertIndex++}:null}`; + break; + case 'object': + let objectInsertResult = this.getInsertTextForObject(schema, separatorAfter, `${indent}\t`, insertIndex++); + insertText = objectInsertResult.insertText.trimLeft(); + insertIndex = objectInsertResult.insertIndex; + break; + } + return { insertText, insertIndex }; + } + private getInsertTextForProperty(key: string, propertySchema: JSONSchema, addValue: boolean, separatorAfter: string): string { let propertyText = this.getInsertTextForValue(key, ''); @@ -536,48 +635,44 @@ export class YAMLCompletion { let resultText = propertyText + ':'; let value; - let nValueProposals = 0; if (propertySchema) { if (propertySchema.default !== undefined) { value = ` \${1:${propertySchema.default}}` } + else if (propertySchema.properties) { + return `${resultText}\n${this.getInsertTextForObject(propertySchema, separatorAfter).insertText}`; + } + else if (propertySchema.items) { + return `${resultText}\n\t- ${this.getInsertTextForArray(propertySchema.items, separatorAfter).insertText}`; + } else { - if (nValueProposals === 0) { - var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type; - if (!type) { - if (propertySchema.properties) { - type = 'object'; - } else if (propertySchema.items) { - type = 'array'; - } - } - switch (type) { - case 'boolean': - value = ' $1'; - break; - case 'string': - value = ' $1'; - break; - case 'object': - value = '\n\t'; - break; - case 'array': - value = '\n\t- '; - break; - case 'number': - case 'integer': - value = ' ${1:0}'; - break; - case 'null': - value = ' ${1:null}'; - break; - default: - return propertyText; - } + var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type; + switch (type) { + case 'boolean': + value = ' $1'; + break; + case 'string': + value = ' $1'; + break; + case 'object': + value = '\n\t'; + break; + case 'array': + value = '\n\t- '; + break; + case 'number': + case 'integer': + value = ' ${1:0}'; + break; + case 'null': + value = ' ${1:null}'; + break; + default: + return propertyText; } } } - if (!value || nValueProposals > 1) { + if (!value) { value = '$1'; } return resultText + value + separatorAfter; diff --git a/test/autoCompletion2.test.ts b/test/autoCompletion2.test.ts index 1f0ceefd..eee91bdd 100644 --- a/test/autoCompletion2.test.ts +++ b/test/autoCompletion2.test.ts @@ -49,8 +49,6 @@ suite("Auto Completion Tests", () => { describe('doComplete', function(){ - - it('Array autocomplete without word', (done) => { let content = "authors:\n - "; let completion = parseSetup(content, 14); @@ -59,6 +57,22 @@ suite("Auto Completion Tests", () => { }).then(done, done); }); + it('Array autocomplete without word on array symbol', (done) => { + let content = "authors:\n -"; + let completion = parseSetup(content, 13); + completion.then(function(result){ + assert.notEqual(result.items.length, 0); + }).then(done, done); + }); + + it('Array autocomplete without word on space before array symbol', (done) => { + let content = "authors:\n - name: test\n " + let completion = parseSetup(content, 24); + completion.then(function(result){ + assert.notEqual(result.items.length, 0); + }).then(done, done); + }); + it('Array autocomplete with letter', (done) => { let content = "authors:\n - n"; let completion = parseSetup(content, 14); @@ -127,14 +141,6 @@ suite("Auto Completion Tests", () => { }).then(done, done); }); - it('Autocompletion does not complete on wrong spot in array node', (done) => { - let content = "authors:\n - name: test\n " - let completion = parseSetup(content, 24); - completion.then(function(result){ - assert.equal(result.items.length, 0); - }).then(done, done); - }); - }); }); diff --git a/test/autoCompletion3.test.ts b/test/autoCompletion3.test.ts index f4ea6677..309e57ec 100644 --- a/test/autoCompletion3.test.ts +++ b/test/autoCompletion3.test.ts @@ -41,7 +41,7 @@ suite("Auto Completion Tests", () => { return completionHelper(testTextDocument, testTextDocument.positionAt(position)); } - it('Array of enum autocomplete without word', (done) => { + it('Array of enum autocomplete without word on array symbol', (done) => { let content = "optionalUnityReferences:\n -"; let completion = parseSetup(content, 29); completion.then(function(result){ From 0f62274db2c132dd82e4464543af6422cac5095f Mon Sep 17 00:00:00 2001 From: Jiayin Pei Date: Fri, 7 Sep 2018 11:16:45 +0800 Subject: [PATCH 2/2] fix autocompletion in case that there is no required property or property with default value --- src/languageservice/services/yamlCompletion.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index 9025dac0..020ed890 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -588,6 +588,9 @@ export class YAMLCompletion { } } }); + if (insertText.trim().length === 0) { + insertText = `${indent}\$${insertIndex++}\n`; + } insertText = insertText.trimRight() + separatorAfter; return { insertText, insertIndex }; }