diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index 99e061f0..b0f97972 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -144,11 +144,11 @@ export class YamlCompletion { this.arrayPrefixIndentation = ''; let overwriteRange: Range = null; + const isOnlyHyphen = lineContent.match(/^\s*(-)\s*($|#)/); if (areOnlySpacesAfterPosition) { overwriteRange = Range.create(position, Position.create(position.line, lineContent.length)); const isOnlyWhitespace = lineContent.trim().length === 0; - const isOnlyDash = lineContent.match(/^\s*(-)\s*$/); - if (node && isScalar(node) && !isOnlyWhitespace && !isOnlyDash) { + if (node && isScalar(node) && !isOnlyWhitespace && !isOnlyHyphen) { const lineToPosition = lineContent.substring(0, position.character); const matches = // get indentation of unfinished property (between indent and cursor) @@ -162,6 +162,8 @@ export class YamlCompletion { Position.create(position.line, lineContent.length) ); } + } else if (node && isScalar(node) && node.value === null && currentWord === '-') { + this.arrayPrefixIndentation = ' '; } } else if (node && isScalar(node) && node.value === 'null') { const nodeStartPos = document.positionAt(node.range[0]); @@ -357,6 +359,12 @@ export class YamlCompletion { if (node) { if (lineContent.length === 0) { node = currentDoc.internalDocument.contents as Node; + } else if (isSeq(node) && isOnlyHyphen) { + const index = this.findItemAtOffset(node, document, offset); + const item = node.items[index]; + if (isNode(item)) { + node = item; + } } else { const parent = currentDoc.getParent(node); if (parent) { @@ -717,19 +725,19 @@ export class YamlCompletion { const propertySchema = schemaProperties[key]; if (typeof propertySchema === 'object' && !propertySchema.deprecationMessage && !propertySchema['doNotSuggest']) { - let identCompensation = ''; + let indentCompensation = ''; if (nodeParent && isSeq(nodeParent) && node.items.length <= 1 && !hasOnlyWhitespace) { - // because there is a slash '-' to prevent the properties generated to have the correct - // indent - const sourceText = textBuffer.getText(); - const indexOfSlash = sourceText.lastIndexOf('-', node.range[0] - 1); - if (indexOfSlash >= 0) { - // add one space to compensate the '-' - const overwriteChars = overwriteRange.end.character - overwriteRange.start.character; - identCompensation = ' ' + sourceText.slice(indexOfSlash + 1, node.range[1] - overwriteChars); + // because there is a slash '-' to prevent the properties generated to have the correct indent + const fromLastHyphenToPosition = lineContent.slice( + lineContent.lastIndexOf('-'), + overwriteRange.start.character + ); + const hyphenFollowedByEmpty = fromLastHyphenToPosition.match(/-([ \t]*)/); + if (hyphenFollowedByEmpty) { + indentCompensation = ' ' + hyphenFollowedByEmpty[1]; } } - identCompensation += this.arrayPrefixIndentation; + indentCompensation += this.arrayPrefixIndentation; // if check that current node has last pair with "null" value and key witch match key from schema, // and if schema has array definition it add completion item for array item creation @@ -760,7 +768,7 @@ export class YamlCompletion { key, propertySchema, separatorAfter, - identCompensation + this.indentation + indentCompensation + this.indentation ); } const isNodeNull = @@ -787,13 +795,13 @@ export class YamlCompletion { key, propertySchema, separatorAfter, - identCompensation + this.indentation + indentCompensation + this.indentation ), insertTextFormat: InsertTextFormat.Snippet, documentation: this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '', parent: { schema: schema.schema, - indent: identCompensation, + indent: indentCompensation, }, }); } diff --git a/test/autoCompletionFix.test.ts b/test/autoCompletionFix.test.ts index 81053315..0011f7db 100644 --- a/test/autoCompletionFix.test.ts +++ b/test/autoCompletionFix.test.ts @@ -865,6 +865,30 @@ objB: }) ); }); + it('indent compensation for partial key with trailing spaces', async () => { + const schema: JSONSchema = { + type: 'object', + properties: { + array: { + type: 'array', + items: { + type: 'object', + properties: { + obj1: { + type: 'object', + }, + }, + }, + }, + }, + }; + schemaProvider.addSchema(SCHEMA_ID, schema); + const content = 'array:\n - obj| | '; + const completion = await parseCaret(content); + + expect(completion.items.length).equal(1); + expect(completion.items[0].insertText).eql('obj1:\n '); + }); describe('partial value with trailing spaces', () => { it('partial value with trailing spaces', async () => { @@ -1085,6 +1109,77 @@ objB: expect(result.items.length).to.be.equal(1); expect(result.items[0].insertText).to.be.equal('objA:\n itemA: '); }); + + it('array completion - should suggest correct indent when extra spaces after cursor followed by with different array item', async () => { + schemaProvider.addSchema(SCHEMA_ID, { + type: 'object', + properties: { + test: { + type: 'array', + items: { + type: 'object', + properties: { + objA: { + type: 'object', + required: ['itemA'], + properties: { + itemA: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }); + const content = ` +test: + - | | + - objA: + itemA: test`; + const result = await parseCaret(content); + + expect(result.items.length).to.be.equal(1); + expect(result.items[0].insertText).to.be.equal('objA:\n itemA: '); + }); + + it('array completion - should suggest correct indent when cursor is just after hyphen with trailing spaces', async () => { + schemaProvider.addSchema(SCHEMA_ID, { + type: 'object', + properties: { + test: { + type: 'array', + items: { + type: 'object', + properties: { + objA: { + type: 'object', + required: ['itemA'], + properties: { + itemA: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }); + const content = ` +test: + -| | +`; + const result = await parseCaret(content); + + expect(result.items.length).to.be.equal(1); + expect(result.items[0].textEdit).to.deep.equal({ + newText: ' objA:\n itemA: ', + // range should contains all the trailing spaces + range: Range.create(2, 3, 2, 9), + }); + }); it('array of arrays completion - should suggest correct indent when extra spaces after cursor', async () => { schemaProvider.addSchema(SCHEMA_ID, { type: 'object', @@ -1161,6 +1256,44 @@ objB: expect(result.items.length).to.be.equal(1); expect(result.items[0].insertText).to.be.equal('objA:\n itemA: '); }); + + describe('array item with existing property', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + array1: { + type: 'array', + items: { + type: 'object', + properties: { + objA: { + type: 'object', + }, + propB: { + const: 'test', + }, + }, + }, + }, + }, + }; + it('should get extra space compensation for the 1st prop in array object item', async () => { + schemaProvider.addSchema(SCHEMA_ID, schema); + const content = 'array1:\n - |\n| propB: test'; + const result = await parseCaret(content); + + expect(result.items.length).to.be.equal(1); + expect(result.items[0].insertText).to.be.equal('objA:\n '); + }); + it('should get extra space compensation for the 1st prop in array object item - extra spaces', async () => { + schemaProvider.addSchema(SCHEMA_ID, schema); + const content = 'array1:\n - | | \n propB: test'; + const result = await parseCaret(content); + + expect(result.items.length).to.be.equal(1); + expect(result.items[0].insertText).to.be.equal('objA:\n '); + }); + }); }); //'extra space after cursor' it('should suggest from additionalProperties', async () => { diff --git a/test/yaml-documents.test.ts b/test/yaml-documents.test.ts index c2278b96..a544ce18 100644 --- a/test/yaml-documents.test.ts +++ b/test/yaml-documents.test.ts @@ -211,16 +211,89 @@ objB: expect(((result as YAMLMap).items[0].key as Scalar).value).eqls('bar'); }); - it('Find closes node: array', () => { - const doc = setupTextDocument('foo:\n - bar: aaa\n '); - const yamlDoc = documents.getYamlDocument(doc); - const textBuffer = new TextBuffer(doc); + describe('Array', () => { + // Problem in `getNodeFromPosition` function. This method doesn't give proper results for arrays + // for example, this yaml return nodes: + // foo: + // - # foo object is returned (should be foo[0]) + // # foo object is returned (should be foo[0]) + // item1: aaaf + // # foo[0] object is returned (OK) + // - # foo object is returned (should be foo[1]) + // # foo[!!0!!] object is returned (should be foo[1]) + // item2: bbb + // # foo[1] object is returned (OK) + + it('Find closes node: array', () => { + const doc = setupTextDocument('foo:\n - bar: aaa\n '); + const yamlDoc = documents.getYamlDocument(doc); + const textBuffer = new TextBuffer(doc); + + const result = yamlDoc.documents[0].findClosestNode(20, textBuffer); + + expect(result).is.not.undefined; + expect(isSeq(result)).is.true; + expect((((result as YAMLSeq).items[0] as YAMLMap).items[0].key as Scalar).value).eqls('bar'); + }); + it.skip('Find first array item node', () => { + const doc = setupTextDocument(`foo: + - + item1: aaa +`); + const yamlDoc = documents.getYamlDocument(doc); + const textBuffer = new TextBuffer(doc); + + const result = yamlDoc.documents[0].findClosestNode(9, textBuffer); + + expect(result).is.not.undefined; + expect(isMap(result)).is.true; + expect(((result as YAMLMap).items[0].key as Scalar).value).eqls('item1'); + }); + it.skip('Find first array item node - extra indent', () => { + const doc = setupTextDocument(`foo: + - + + item1: aaa +`); + const yamlDoc = documents.getYamlDocument(doc); + const textBuffer = new TextBuffer(doc); + + const result = yamlDoc.documents[0].findClosestNode(9, textBuffer); + + expect(result).is.not.undefined; + expect(isMap(result)).is.true; + expect(((result as YAMLMap).items[0].key as Scalar).value).eqls('item1'); + }); + + it.skip('Find second array item node', () => { + const doc = setupTextDocument(`foo: + - item1: aaa + - + item2: bbb`); + const yamlDoc = documents.getYamlDocument(doc); + const textBuffer = new TextBuffer(doc); + + const result = yamlDoc.documents[0].findClosestNode(24, textBuffer); + + expect(result).is.not.undefined; + expect(isMap(result)).is.true; + expect(((result as YAMLMap).items[0].key as Scalar).value).eqls('item2'); + }); + it.skip('Find second array item node: - extra indent', () => { + const doc = setupTextDocument(`foo: + - item1: aaa + - + + item2: bbb`); + const yamlDoc = documents.getYamlDocument(doc); + const textBuffer = new TextBuffer(doc); - const result = yamlDoc.documents[0].findClosestNode(20, textBuffer); + const result = yamlDoc.documents[0].findClosestNode(28, textBuffer); - expect(result).is.not.undefined; - expect(isSeq(result)).is.true; - expect((((result as YAMLSeq).items[0] as YAMLMap).items[0].key as Scalar).value).eqls('bar'); + expect(result).is.not.undefined; + expect(isMap(result)).is.true; + expect(((result as YAMLMap).items[0].key as Scalar).value).eqls('item2'); + }); }); it('Find closes node: root map', () => {