Skip to content

Commit da6d9bf

Browse files
Merge pull request #691 from p-spacek/feat/select-parent-skeleton-first
Select parent skeleton first before other properties are suggested
2 parents cb85ff7 + 1722770 commit da6d9bf

File tree

6 files changed

+136
-9
lines changed

6 files changed

+136
-9
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ The following settings are supported:
4949
- `http.proxyStrictSSL`: If true the proxy server certificate should be verified against the list of supplied CAs. Default is false.
5050
- `[yaml].editor.formatOnType`: Enable/disable on type indent and auto formatting array
5151
- `yaml.disableDefaultProperties`: Disable adding not required properties with default values into completion text
52+
- `yaml.suggest.parentSkeletonSelectedFirst`: If true, the user must select some parent skeleton first before autocompletion starts to suggest the rest of the properties.\nWhen yaml object is not empty, autocompletion ignores this setting and returns all properties and skeletons.
5253

5354
##### Adding custom tags
5455

src/languageserver/handlers/settingsHandlers.ts

+5
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ export class SettingsHandler {
105105
}
106106
this.yamlSettings.disableAdditionalProperties = settings.yaml.disableAdditionalProperties;
107107
this.yamlSettings.disableDefaultProperties = settings.yaml.disableDefaultProperties;
108+
109+
if (settings.yaml.suggest) {
110+
this.yamlSettings.suggest.parentSkeletonSelectedFirst = settings.yaml.suggest.parentSkeletonSelectedFirst;
111+
}
108112
}
109113

110114
this.yamlSettings.schemaConfigurationSettings = [];
@@ -232,6 +236,7 @@ export class SettingsHandler {
232236
indentation: this.yamlSettings.indentation,
233237
disableAdditionalProperties: this.yamlSettings.disableAdditionalProperties,
234238
disableDefaultProperties: this.yamlSettings.disableDefaultProperties,
239+
parentSkeletonSelectedFirst: this.yamlSettings.suggest.parentSkeletonSelectedFirst,
235240
yamlVersion: this.yamlSettings.yamlVersion,
236241
};
237242

src/languageservice/services/yamlCompletion.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export class YamlCompletion {
7373
private indentation: string;
7474
private supportsMarkdown: boolean | undefined;
7575
private disableDefaultProperties: boolean;
76+
private parentSkeletonSelectedFirst: boolean;
7677

7778
constructor(
7879
private schemaService: YAMLSchemaService,
@@ -89,6 +90,7 @@ export class YamlCompletion {
8990
this.yamlVersion = languageSettings.yamlVersion;
9091
this.configuredIndentation = languageSettings.indentation;
9192
this.disableDefaultProperties = languageSettings.disableDefaultProperties;
93+
this.parentSkeletonSelectedFirst = languageSettings.parentSkeletonSelectedFirst;
9294
}
9395

9496
async doComplete(document: TextDocument, position: Position, isKubernetes = false): Promise<CompletionList> {
@@ -589,7 +591,6 @@ export class YamlCompletion {
589591
const lineContent = textBuffer.getLineContent(overwriteRange.start.line);
590592
const hasOnlyWhitespace = lineContent.trim().length === 0;
591593
const hasColon = lineContent.indexOf(':') !== -1;
592-
593594
const nodeParent = doc.getParent(node);
594595
const matchOriginal = matchingSchemas.find((it) => it.node.internalNode === originalNode && it.schema.properties);
595596
for (const schema of matchingSchemas) {
@@ -676,14 +677,19 @@ export class YamlCompletion {
676677
identCompensation + this.indentation
677678
);
678679
}
679-
680-
collector.add({
681-
kind: CompletionItemKind.Property,
682-
label: key,
683-
insertText,
684-
insertTextFormat: InsertTextFormat.Snippet,
685-
documentation: this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
686-
});
680+
const isNodeNull =
681+
(isScalar(originalNode) && originalNode.value === null) ||
682+
(isMap(originalNode) && originalNode.items.length === 0);
683+
const existsParentCompletion = schema.schema.required?.length > 0;
684+
if (!this.parentSkeletonSelectedFirst || !isNodeNull || !existsParentCompletion) {
685+
collector.add({
686+
kind: CompletionItemKind.Property,
687+
label: key,
688+
insertText,
689+
insertTextFormat: InsertTextFormat.Snippet,
690+
documentation: this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
691+
});
692+
}
687693
// if the prop is required add it also to parent suggestion
688694
if (schema.schema.required?.includes(key)) {
689695
collector.add({

src/languageservice/yamlLanguageService.ts

+6
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ export interface LanguageSettings {
9898
*/
9999
disableDefaultProperties?: boolean;
100100

101+
/**
102+
* If true, the user must select some parent skeleton first before autocompletion starts to suggest the rest of the properties.
103+
* When yaml object is not empty, autocompletion ignores this setting and returns all properties and skeletons.
104+
*/
105+
parentSkeletonSelectedFirst?: boolean;
106+
101107
/**
102108
* Default yaml lang version
103109
*/

src/yamlSettings.ts

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export interface Settings {
2222
};
2323
disableDefaultProperties: boolean;
2424
disableAdditionalProperties: boolean;
25+
suggest: {
26+
parentSkeletonSelectedFirst: boolean;
27+
};
2528
maxItemsComputed: number;
2629
yamlVersion: YamlVersion;
2730
};
@@ -69,6 +72,9 @@ export class SettingsState {
6972
indentation: string | undefined = undefined;
7073
disableAdditionalProperties = false;
7174
disableDefaultProperties = false;
75+
suggest = {
76+
parentSkeletonSelectedFirst: false,
77+
};
7278
maxItemsComputed = 5000;
7379

7480
// File validation helpers

test/autoCompletion.test.ts

+103
Original file line numberDiff line numberDiff line change
@@ -2795,5 +2795,108 @@ describe('Auto Completion Tests', () => {
27952795
})
27962796
);
27972797
});
2798+
describe('Select parent skeleton first', () => {
2799+
beforeEach(() => {
2800+
const languageSettingsSetup = new ServiceSetup().withCompletion();
2801+
languageSettingsSetup.languageSettings.parentSkeletonSelectedFirst = true;
2802+
languageService.configure(languageSettingsSetup.languageSettings);
2803+
});
2804+
it('Should suggest complete object skeleton', async () => {
2805+
const schema = {
2806+
definitions: { obj1, obj2 },
2807+
anyOf: [
2808+
{
2809+
$ref: '#/definitions/obj1',
2810+
},
2811+
{
2812+
$ref: '#/definitions/obj2',
2813+
},
2814+
],
2815+
};
2816+
languageService.addSchema(SCHEMA_ID, schema);
2817+
const content = '';
2818+
const result = await parseSetup(content, content.length);
2819+
2820+
expect(result.items.map((i) => i.label)).to.have.members(['Object1', 'obj2']);
2821+
});
2822+
it('Should suggest complete object skeleton - nested', async () => {
2823+
const schema = {
2824+
definitions: { obj1, obj2 },
2825+
properties: {
2826+
name: {
2827+
anyOf: [
2828+
{
2829+
$ref: '#/definitions/obj1',
2830+
},
2831+
{
2832+
$ref: '#/definitions/obj2',
2833+
},
2834+
],
2835+
},
2836+
},
2837+
};
2838+
languageService.addSchema(SCHEMA_ID, schema);
2839+
const content = 'name:\n ';
2840+
const result = await parseSetup(content, content.length);
2841+
2842+
expect(result.items.map((i) => i.label)).to.have.members(['Object1', 'obj2']);
2843+
});
2844+
it('Should suggest complete object skeleton - array', async () => {
2845+
const schema = {
2846+
definitions: { obj1, obj2 },
2847+
items: {
2848+
anyOf: [
2849+
{
2850+
$ref: '#/definitions/obj1',
2851+
},
2852+
{
2853+
$ref: '#/definitions/obj2',
2854+
},
2855+
],
2856+
},
2857+
type: 'array',
2858+
};
2859+
languageService.addSchema(SCHEMA_ID, schema);
2860+
const content = '- ';
2861+
const result = await parseSetup(content, content.length);
2862+
2863+
expect(result.items.map((i) => i.label)).to.have.members(['Object1', 'obj2']);
2864+
});
2865+
it('Should suggest rest of the parent object', async () => {
2866+
const schema = {
2867+
definitions: { obj1 },
2868+
$ref: '#/definitions/obj1',
2869+
};
2870+
languageService.addSchema(SCHEMA_ID, schema);
2871+
const content = 'type: typeObj1\n';
2872+
const result = await parseSetup(content, content.length);
2873+
2874+
expect(result.items.map((i) => i.label)).to.have.members(['options', 'Object1']);
2875+
});
2876+
it('Should suggest all feature when user is typing', async () => {
2877+
const schema = {
2878+
definitions: { obj1 },
2879+
$ref: '#/definitions/obj1',
2880+
};
2881+
languageService.addSchema(SCHEMA_ID, schema);
2882+
const content = 'ty';
2883+
const result = await parseSetup(content, content.length);
2884+
2885+
expect(result.items.map((i) => i.label)).to.have.members(['type', 'options', 'Object1']);
2886+
});
2887+
it('Should suggest all properties in empty yaml with now required props', async () => {
2888+
const schema = {
2889+
properties: {
2890+
fruit: {},
2891+
vegetable: {},
2892+
},
2893+
};
2894+
languageService.addSchema(SCHEMA_ID, schema);
2895+
const content = '';
2896+
const result = await parseSetup(content, content.length);
2897+
2898+
expect(result.items.map((i) => i.label)).to.have.members(['fruit', 'vegetable']);
2899+
});
2900+
});
27982901
});
27992902
});

0 commit comments

Comments
 (0)