Skip to content

Commit bcb28b7

Browse files
authored
Fix code completion with object in array (redhat-developer#562)
Signed-off-by: Yevhen Vydolob <yvydolob@redhat.com>
1 parent f27da65 commit bcb28b7

File tree

3 files changed

+102
-23
lines changed

3 files changed

+102
-23
lines changed

src/languageservice/services/yamlCompletion.ts

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { stringifyObject, StringifySettings } from '../utils/json';
3131
import { isDefined, isString } from '../utils/objects';
3232
import * as nls from 'vscode-nls';
3333
import { setKubernetesParserOption } from '../parser/isKubernetes';
34+
import { isMapContainsEmptyPair } from '../utils/astUtils';
3435
import { indexOf } from '../utils/astUtils';
3536

3637
const localize = nls.loadMessageBundle();
@@ -242,28 +243,34 @@ export class YamlCompletion {
242243
}
243244
}
244245
} else if (node.value === null) {
245-
if (isPair(parent) && parent.key === node) {
246-
node = parent;
247-
} else if (
248-
isPair(parent) &&
249-
lineContent.trim().length === 0 &&
250-
textBuffer.getLineContent(position.line - 1).indexOf(':') > 0 &&
251-
textBuffer.getLineContent(position.line - 1).indexOf('-') < 0
252-
) {
253-
const map = this.createTempObjNode(currentWord, node, currentDoc);
254-
255-
const parentParent = currentDoc.getParent(parent);
256-
if (parentParent && (isMap(parentParent) || isSeq(parentParent))) {
257-
parentParent.set(parent.key, map);
246+
if (isPair(parent)) {
247+
if (parent.key === node) {
248+
node = parent;
258249
} else {
259-
currentDoc.internalDocument.set(parent.key, map);
260-
}
261-
currentProperty = (map as YAMLMap).items[0];
262-
node = map;
263-
} else if (lineContent.trim().length === 0) {
264-
const parentParent = currentDoc.getParent(parent);
265-
if (parentParent) {
266-
node = parentParent;
250+
if (isNode(parent.key) && parent.key.range) {
251+
const parentParent = currentDoc.getParent(parent);
252+
if (foundByClosest && parentParent && isMap(parentParent) && isMapContainsEmptyPair(parentParent)) {
253+
node = parentParent;
254+
} else {
255+
const parentPosition = document.positionAt(parent.key.range[0]);
256+
//if cursor has bigger indentation that parent key, then we need to complete new empty object
257+
if (position.character > parentPosition.character && position.line !== parentPosition.line) {
258+
const map = this.createTempObjNode(currentWord, node, currentDoc);
259+
260+
if (parentParent && (isMap(parentParent) || isSeq(parentParent))) {
261+
parentParent.set(parent.key, map);
262+
} else {
263+
currentDoc.internalDocument.set(parent.key, map);
264+
}
265+
currentProperty = (map as YAMLMap).items[0];
266+
node = map;
267+
} else if (parentPosition.character === position.character) {
268+
if (parentParent) {
269+
node = parentParent;
270+
}
271+
}
272+
}
273+
}
267274
}
268275
} else if (isSeq(parent)) {
269276
if (lineContent.charAt(position.character - 1) !== '-') {
@@ -373,7 +380,12 @@ export class YamlCompletion {
373380
const schemaProperties = schema.schema.properties;
374381
if (schemaProperties) {
375382
const maxProperties = schema.schema.maxProperties;
376-
if (maxProperties === undefined || node.items === undefined || node.items.length < maxProperties) {
383+
if (
384+
maxProperties === undefined ||
385+
node.items === undefined ||
386+
node.items.length < maxProperties ||
387+
isMapContainsEmptyPair(node)
388+
) {
377389
for (const key in schemaProperties) {
378390
if (Object.prototype.hasOwnProperty.call(schemaProperties, key)) {
379391
const propertySchema = schemaProperties[key];

src/languageservice/utils/astUtils.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Document, isDocument, Node, visit, YAMLSeq } from 'yaml';
6+
import { Document, isDocument, isScalar, Node, visit, YAMLMap, YAMLSeq } from 'yaml';
77

88
export function getParent(doc: Document, nodeToFind: Node): Node | undefined {
99
let parentNode: Node;
@@ -20,6 +20,18 @@ export function getParent(doc: Document, nodeToFind: Node): Node | undefined {
2020

2121
return parentNode;
2222
}
23+
export function isMapContainsEmptyPair(map: YAMLMap): boolean {
24+
if (map.items.length > 1) {
25+
return false;
26+
}
27+
28+
const pair = map.items[0];
29+
if (isScalar(pair.key) && isScalar(pair.value) && pair.key.value === '' && !pair.value.value) {
30+
return true;
31+
}
32+
33+
return false;
34+
}
2335

2436
export function indexOf(seq: YAMLSeq, item: Node): number | undefined {
2537
for (const [i, obj] of seq.items.entries()) {

test/autoCompletionFix.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { SettingsState, TextDocumentTestManager } from '../src/yamlSettings';
1010
import { ServiceSetup } from './utils/serviceSetup';
1111
import { SCHEMA_ID, setupLanguageService, setupSchemaIDTextDocument } from './utils/testHelper';
1212
import { expect } from 'chai';
13+
import { createExpectedCompletion } from './utils/verifyError';
1314

1415
describe('Auto Completion Fix Tests', () => {
1516
let languageSettingsSetup: ServiceSetup;
@@ -45,6 +46,60 @@ describe('Auto Completion Fix Tests', () => {
4546
languageService.configure(languageSettingsSetup.languageSettings);
4647
});
4748

49+
it('should show completion on map under array', async () => {
50+
languageService.addSchema(SCHEMA_ID, {
51+
type: 'array',
52+
items: {
53+
type: 'object',
54+
properties: {
55+
from: {
56+
type: 'object',
57+
properties: {
58+
foo: {
59+
type: 'boolean',
60+
},
61+
},
62+
},
63+
},
64+
},
65+
});
66+
const content = '- from:\n ';
67+
const completion = await parseSetup(content, 1, 3);
68+
expect(completion.items).lengthOf(1);
69+
expect(completion.items[0]).eql(
70+
createExpectedCompletion('foo', 'foo: $1', 1, 3, 1, 3, 10, 2, {
71+
documentation: '',
72+
})
73+
);
74+
});
75+
76+
it('should show completion on array empty array item', async () => {
77+
languageService.addSchema(SCHEMA_ID, {
78+
type: 'array',
79+
items: {
80+
type: 'object',
81+
properties: {
82+
from: {
83+
type: 'object',
84+
properties: {
85+
foo: {
86+
type: 'boolean',
87+
},
88+
},
89+
},
90+
},
91+
},
92+
});
93+
const content = '- ';
94+
const completion = await parseSetup(content, 0, 2);
95+
expect(completion.items).lengthOf(1);
96+
expect(completion.items[0]).eql(
97+
createExpectedCompletion('from', 'from:\n $1', 0, 2, 0, 2, 10, 2, {
98+
documentation: '',
99+
})
100+
);
101+
});
102+
48103
it('should show completion items in the middle of map in array', async () => {
49104
const content = `apiVersion: v1
50105
kind: Pod

0 commit comments

Comments
 (0)