From f61734ccc9fa964b2d2efe596a7c63b214d11c7f Mon Sep 17 00:00:00 2001
From: Petr Spacek
Date: Wed, 17 May 2023 10:23:22 +0200
Subject: [PATCH 1/4] fix: array item with existing property
---
.../services/yamlCompletion.ts | 36 ++++++-----
test/autoCompletionFix.test.ts | 62 +++++++++++++++++++
2 files changed, 83 insertions(+), 15 deletions(-)
diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts
index 99e061f0b..b2efbd680 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)
@@ -357,6 +357,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 +723,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 +766,7 @@ export class YamlCompletion {
key,
propertySchema,
separatorAfter,
- identCompensation + this.indentation
+ indentCompensation + this.indentation
);
}
const isNodeNull =
@@ -787,13 +793,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 81053315b..a88b9787c 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',
+ },
+ },
+ },
+ },
+ },
+ };
+ languageService.addSchema(SCHEMA_ID, schema);
+ const content = 'array:\n - na ';
+ const completion = await parseSetup(content, 1, 5);
+
+ 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 () => {
@@ -1161,6 +1185,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 () => {
+ languageService.addSchema(SCHEMA_ID, schema);
+ const content = 'array1:\n - \n propB: test';
+ const result = await parseSetup(content, 1, 4); // after `- `
+
+ 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 () => {
+ languageService.addSchema(SCHEMA_ID, schema);
+ const content = 'array1:\n - \n propB: test';
+ const result = await parseSetup(content, 1, 4); // after `- `
+
+ 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 () => {
From be8ace585a8bc30a65c545e829842d9f5fbb0ff3 Mon Sep 17 00:00:00 2001
From: Petr Spacek
Date: Tue, 6 Jun 2023 11:13:29 +0200
Subject: [PATCH 2/4] fix: some PR comments
---
.../services/yamlCompletion.ts | 2 +-
test/autoCompletionFix.test.ts | 21 +++--
test/yaml-documents.test.ts | 77 +++++++++++++++++--
3 files changed, 85 insertions(+), 15 deletions(-)
diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts
index b2efbd680..0e73a54d5 100644
--- a/src/languageservice/services/yamlCompletion.ts
+++ b/src/languageservice/services/yamlCompletion.ts
@@ -144,7 +144,7 @@ export class YamlCompletion {
this.arrayPrefixIndentation = '';
let overwriteRange: Range = null;
- const isOnlyHyphen = lineContent.match(/^\s*(-)\s*$/);
+ const isOnlyHyphen = lineContent.match(/^\s*(-)\s*($|#)/);
if (areOnlySpacesAfterPosition) {
overwriteRange = Range.create(position, Position.create(position.line, lineContent.length));
const isOnlyWhitespace = lineContent.trim().length === 0;
diff --git a/test/autoCompletionFix.test.ts b/test/autoCompletionFix.test.ts
index a88b9787c..2486246ad 100644
--- a/test/autoCompletionFix.test.ts
+++ b/test/autoCompletionFix.test.ts
@@ -883,8 +883,8 @@ objB:
},
};
languageService.addSchema(SCHEMA_ID, schema);
- const content = 'array:\n - na ';
- const completion = await parseSetup(content, 1, 5);
+ const content = 'array:\n - na| | ';
+ const completion = await parseCaret(content);
expect(completion.items.length).equal(1);
expect(completion.items[0].insertText).eql('obj1:\n ');
@@ -1208,16 +1208,25 @@ objB:
};
it('should get extra space compensation for the 1st prop in array object item', async () => {
languageService.addSchema(SCHEMA_ID, schema);
- const content = 'array1:\n - \n propB: test';
- const result = await parseSetup(content, 1, 4); // after `- `
+ 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 () => {
languageService.addSchema(SCHEMA_ID, schema);
- const content = 'array1:\n - \n propB: test';
- const result = await parseSetup(content, 1, 4); // after `- `
+ 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 ');
+ });
+ // previous PR doesn't fix this
+ it.skip('should get extra space compensation for the 1st prop in array object item - extra lines', async () => {
+ languageService.addSchema(SCHEMA_ID, schema);
+ const content = 'array1:\n - \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 ');
diff --git a/test/yaml-documents.test.ts b/test/yaml-documents.test.ts
index c2278b969..d4c08618f 100644
--- a/test/yaml-documents.test.ts
+++ b/test/yaml-documents.test.ts
@@ -211,16 +211,77 @@ 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', () => {
+ 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', () => {
From 7ca0300bd529d383f310b6778b58b64b5e41aeb9 Mon Sep 17 00:00:00 2001
From: Petr Spacek
Date: Mon, 17 Jul 2023 13:00:47 +0200
Subject: [PATCH 3/4] chore: add another test and fix failing tests
---
test/autoCompletionFix.test.ts | 52 +++++++++++++++++++++++++---------
test/yaml-documents.test.ts | 12 ++++++++
2 files changed, 51 insertions(+), 13 deletions(-)
diff --git a/test/autoCompletionFix.test.ts b/test/autoCompletionFix.test.ts
index 2486246ad..535fd5e9b 100644
--- a/test/autoCompletionFix.test.ts
+++ b/test/autoCompletionFix.test.ts
@@ -882,8 +882,8 @@ objB:
},
},
};
- languageService.addSchema(SCHEMA_ID, schema);
- const content = 'array:\n - na| | ';
+ schemaProvider.addSchema(SCHEMA_ID, schema);
+ const content = 'array:\n - obj| | ';
const completion = await parseCaret(content);
expect(completion.items.length).equal(1);
@@ -1109,6 +1109,41 @@ 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 of arrays completion - should suggest correct indent when extra spaces after cursor', async () => {
schemaProvider.addSchema(SCHEMA_ID, {
type: 'object',
@@ -1207,7 +1242,7 @@ objB:
},
};
it('should get extra space compensation for the 1st prop in array object item', async () => {
- languageService.addSchema(SCHEMA_ID, schema);
+ schemaProvider.addSchema(SCHEMA_ID, schema);
const content = 'array1:\n - |\n| propB: test';
const result = await parseCaret(content);
@@ -1215,19 +1250,10 @@ objB:
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 () => {
- languageService.addSchema(SCHEMA_ID, schema);
+ 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 ');
- });
- // previous PR doesn't fix this
- it.skip('should get extra space compensation for the 1st prop in array object item - extra lines', async () => {
- languageService.addSchema(SCHEMA_ID, schema);
- const content = 'array1:\n - \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 ');
});
diff --git a/test/yaml-documents.test.ts b/test/yaml-documents.test.ts
index d4c08618f..a544ce18e 100644
--- a/test/yaml-documents.test.ts
+++ b/test/yaml-documents.test.ts
@@ -212,6 +212,18 @@ objB:
});
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);
From 5594fea7359003464150e23a1fd621a053b12b41 Mon Sep 17 00:00:00 2001
From: Petr Spacek
Date: Wed, 29 Nov 2023 13:51:34 +0100
Subject: [PATCH 4/4] fix: correct indent when cursor is just after hyphen with
trailing spaces
---
.../services/yamlCompletion.ts | 2 ++
test/autoCompletionFix.test.ts | 36 +++++++++++++++++++
2 files changed, 38 insertions(+)
diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts
index 0e73a54d5..b0f979723 100644
--- a/src/languageservice/services/yamlCompletion.ts
+++ b/src/languageservice/services/yamlCompletion.ts
@@ -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]);
diff --git a/test/autoCompletionFix.test.ts b/test/autoCompletionFix.test.ts
index 535fd5e9b..0011f7dbc 100644
--- a/test/autoCompletionFix.test.ts
+++ b/test/autoCompletionFix.test.ts
@@ -1144,6 +1144,42 @@ test:
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',