Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select parent skeleton first before other properties are suggested #691

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ The following settings are supported:
- `http.proxyStrictSSL`: If true the proxy server certificate should be verified against the list of supplied CAs. Default is false.
- `[yaml].editor.formatOnType`: Enable/disable on type indent and auto formatting array
- `yaml.disableDefaultProperties`: Disable adding not required properties with default values into completion text
- `yaml.selectParentSkeletonFirst`: 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.

##### Adding custom tags

Expand Down
2 changes: 2 additions & 0 deletions src/languageserver/handlers/settingsHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class SettingsHandler {
}
this.yamlSettings.disableAdditionalProperties = settings.yaml.disableAdditionalProperties;
this.yamlSettings.disableDefaultProperties = settings.yaml.disableDefaultProperties;
this.yamlSettings.selectParentSkeletonFirst = settings.yaml.selectParentSkeletonFirst;
}

this.yamlSettings.schemaConfigurationSettings = [];
Expand Down Expand Up @@ -232,6 +233,7 @@ export class SettingsHandler {
indentation: this.yamlSettings.indentation,
disableAdditionalProperties: this.yamlSettings.disableAdditionalProperties,
disableDefaultProperties: this.yamlSettings.disableDefaultProperties,
selectParentSkeletonFirst: this.yamlSettings.selectParentSkeletonFirst,
yamlVersion: this.yamlSettings.yamlVersion,
};

Expand Down
23 changes: 14 additions & 9 deletions src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class YamlCompletion {
private indentation: string;
private supportsMarkdown: boolean | undefined;
private disableDefaultProperties: boolean;
private selectParentSkeletonFirst: boolean;

constructor(
private schemaService: YAMLSchemaService,
Expand All @@ -89,6 +90,7 @@ export class YamlCompletion {
this.yamlVersion = languageSettings.yamlVersion;
this.configuredIndentation = languageSettings.indentation;
this.disableDefaultProperties = languageSettings.disableDefaultProperties;
this.selectParentSkeletonFirst = languageSettings.selectParentSkeletonFirst;
}

async doComplete(document: TextDocument, position: Position, isKubernetes = false): Promise<CompletionList> {
Expand Down Expand Up @@ -589,7 +591,8 @@ export class YamlCompletion {
const lineContent = textBuffer.getLineContent(overwriteRange.start.line);
const hasOnlyWhitespace = lineContent.trim().length === 0;
const hasColon = lineContent.indexOf(':') !== -1;

const isNodeNull =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we initalize isNodeNull this early? Are there other places it is used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it down neer to usage

(isScalar(originalNode) && originalNode.value === null) || (isMap(originalNode) && originalNode.items.length === 0);
const nodeParent = doc.getParent(node);
const matchOriginal = matchingSchemas.find((it) => it.node.internalNode === originalNode && it.schema.properties);
for (const schema of matchingSchemas) {
Expand Down Expand Up @@ -676,14 +679,16 @@ export class YamlCompletion {
identCompensation + this.indentation
);
}

collector.add({
kind: CompletionItemKind.Property,
label: key,
insertText,
insertTextFormat: InsertTextFormat.Snippet,
documentation: this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
});
const existsParentCompletion = schema.schema.required?.length > 0;
if (!this.selectParentSkeletonFirst || !isNodeNull || !existsParentCompletion) {
collector.add({
kind: CompletionItemKind.Property,
label: key,
insertText,
insertTextFormat: InsertTextFormat.Snippet,
documentation: this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
});
}
// if the prop is required add it also to parent suggestion
if (schema.schema.required?.includes(key)) {
collector.add({
Expand Down
6 changes: 6 additions & 0 deletions src/languageservice/yamlLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ export interface LanguageSettings {
*/
disableDefaultProperties?: boolean;

/**
* If true, the user must select some parent skeleton first before autocompletion starts to suggest the rest of the properties.
* When yaml object is not empty, autocompletion ignores this setting and returns all properties and skeletons.
*/
selectParentSkeletonFirst?: boolean;

/**
* Default yaml lang version
*/
Expand Down
2 changes: 2 additions & 0 deletions src/yamlSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface Settings {
};
disableDefaultProperties: boolean;
disableAdditionalProperties: boolean;
selectParentSkeletonFirst: boolean;
maxItemsComputed: number;
yamlVersion: YamlVersion;
};
Expand Down Expand Up @@ -69,6 +70,7 @@ export class SettingsState {
indentation: string | undefined = undefined;
disableAdditionalProperties = false;
disableDefaultProperties = false;
selectParentSkeletonFirst = false;
maxItemsComputed = 5000;

// File validation helpers
Expand Down
103 changes: 103 additions & 0 deletions test/autoCompletion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2795,5 +2795,108 @@ describe('Auto Completion Tests', () => {
})
);
});
describe('Select parent skeleton first', () => {
beforeEach(() => {
const languageSettingsSetup = new ServiceSetup().withCompletion();
languageSettingsSetup.languageSettings.selectParentSkeletonFirst = true;
languageService.configure(languageSettingsSetup.languageSettings);
});
it('Should suggest complete object skeleton', async () => {
const schema = {
definitions: { obj1, obj2 },
anyOf: [
{
$ref: '#/definitions/obj1',
},
{
$ref: '#/definitions/obj2',
},
],
};
languageService.addSchema(SCHEMA_ID, schema);
const content = '';
const result = await parseSetup(content, content.length);

expect(result.items.map((i) => i.label)).to.have.members(['Object1', 'obj2']);
});
it('Should suggest complete object skeleton - nested', async () => {
const schema = {
definitions: { obj1, obj2 },
properties: {
name: {
anyOf: [
{
$ref: '#/definitions/obj1',
},
{
$ref: '#/definitions/obj2',
},
],
},
},
};
languageService.addSchema(SCHEMA_ID, schema);
const content = 'name:\n ';
const result = await parseSetup(content, content.length);

expect(result.items.map((i) => i.label)).to.have.members(['Object1', 'obj2']);
});
it('Should suggest complete object skeleton - array', async () => {
const schema = {
definitions: { obj1, obj2 },
items: {
anyOf: [
{
$ref: '#/definitions/obj1',
},
{
$ref: '#/definitions/obj2',
},
],
},
type: 'array',
};
languageService.addSchema(SCHEMA_ID, schema);
const content = '- ';
const result = await parseSetup(content, content.length);

expect(result.items.map((i) => i.label)).to.have.members(['Object1', 'obj2']);
});
it('Should suggest rest of the parent object', async () => {
const schema = {
definitions: { obj1 },
$ref: '#/definitions/obj1',
};
languageService.addSchema(SCHEMA_ID, schema);
const content = 'type: typeObj1\n';
const result = await parseSetup(content, content.length);

expect(result.items.map((i) => i.label)).to.have.members(['options', 'Object1']);
});
it('Should suggest all feature when user is typing', async () => {
const schema = {
definitions: { obj1 },
$ref: '#/definitions/obj1',
};
languageService.addSchema(SCHEMA_ID, schema);
const content = 'ty';
const result = await parseSetup(content, content.length);

expect(result.items.map((i) => i.label)).to.have.members(['type', 'options', 'Object1']);
});
it('Should suggest all properties in empty yaml with now required props', async () => {
const schema = {
properties: {
fruit: {},
vegetable: {},
},
};
languageService.addSchema(SCHEMA_ID, schema);
const content = '';
const result = await parseSetup(content, content.length);

expect(result.items.map((i) => i.label)).to.have.members(['fruit', 'vegetable']);
});
});
});
});