Skip to content

Commit

Permalink
Merge pull request #226 from redhat-developer/defaultSnippets-fix
Browse files Browse the repository at this point in the history
Fixed issue with arrays in default snippets
  • Loading branch information
JPinkney authored Jan 24, 2020
2 parents d613734 + 2584233 commit 3d79d59
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 31 deletions.
29 changes: 20 additions & 9 deletions src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,13 @@ export class YAMLCompletion extends JSONCompletion {
const matchingSchemas = doc.getMatchingSchemas(schema.schema);
matchingSchemas.forEach(s => {
if (s.node === node && !s.inverted) {
this.collectDefaultSnippets(s.schema, separatorAfter, collector, {
newLineFirst: false,
indentFirstObject: false,
shouldIndentWithTab: false
}, false);
const schemaProperties = s.schema.properties;
if (schemaProperties) {
this.collectDefaultSnippets(s.schema, separatorAfter, collector, {
newLineFirst: false,
indentFirstObject: false,
shouldIndentWithTab: false
}, false);
Object.keys(schemaProperties).forEach((key: string) => {
const propertySchema = schemaProperties[key];
if (typeof propertySchema === 'object' && !propertySchema.deprecationMessage && !propertySchema['doNotSuggest']) {
Expand Down Expand Up @@ -439,7 +439,7 @@ export class YAMLCompletion extends JSONCompletion {
}
}

private collectDefaultSnippets(schema: JSONSchema, separatorAfter: string, collector: CompletionsCollector, settings: StringifySettings, isArray: boolean) {
private collectDefaultSnippets(schema: JSONSchema, separatorAfter: string, collector: CompletionsCollector, settings: StringifySettings, isArray: boolean, arrayDepth = 0) {
if (Array.isArray(schema.defaultSnippets)) {
schema.defaultSnippets.forEach(s => {
let type = schema.type;
Expand All @@ -448,10 +448,21 @@ export class YAMLCompletion extends JSONCompletion {
let insertText: string;
let filterText: string;
if (isDefined(value)) {
let type = schema.type;
for (let i = arrayDepth; i > 0; i--) {
value = [value];
type = 'array';
}
insertText = this.getInsertTextForSnippetValue(value, separatorAfter, settings, isArray);
label = label || this.getLabelForSnippetValue(value);
} else if (typeof s.bodyText === 'string') {
let prefix = '', suffix = '', indent = '';
for (let i = arrayDepth; i > 0; i--) {
prefix = prefix + indent + '[\n';
suffix = suffix + '\n' + indent + ']';
indent += '\t';
type = 'array';
}
insertText = prefix + indent + s.bodyText.split('\n').join('\n' + indent) + suffix + separatorAfter;
label = label || insertText;
filterText = insertText.replace(/[\n]/g, ''); // remove new lines
Expand All @@ -469,7 +480,7 @@ export class YAMLCompletion extends JSONCompletion {
}

// tslint:disable-next-line:no-any
private getInsertTextForSnippetValue(value: any, separatorAfter: string, settings: StringifySettings, isArray?: boolean): string {
private getInsertTextForSnippetValue(value: any, separatorAfter: string, settings: StringifySettings, isArray?: boolean, depth?: number): string {
// tslint:disable-next-line:no-any
const replacer = (value: any) => {
if (typeof value === 'string') {
Expand All @@ -492,7 +503,7 @@ export class YAMLCompletion extends JSONCompletion {
});
value = fixedObj;
}
return stringifyObject(value, '', replacer, settings) + separatorAfter;
return stringifyObject(value, '', replacer, settings, depth) + separatorAfter;
}

// tslint:disable-next-line:no-any
Expand Down Expand Up @@ -669,7 +680,7 @@ export class YAMLCompletion extends JSONCompletion {
newLineFirst: true,
indentFirstObject: false,
shouldIndentWithTab: false
});
}, false, 1);
}
}
nValueProposals += propertySchema.defaultSnippets.length;
Expand Down
58 changes: 42 additions & 16 deletions src/languageservice/utils/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,51 @@ export interface StringifySettings {
shouldIndentWithTab: boolean;
}

export function stringifyObject(obj: any, indent: string, stringifyLiteral: (val: any) => string, settings: StringifySettings): string {
// tslint:disable-next-line: no-any
export function stringifyObject(obj: any, indent: string, stringifyLiteral: (val: any) => string, settings: StringifySettings, depth = 0): string {
if (obj !== null && typeof obj === 'object') {
const newIndent = settings.shouldIndentWithTab ? (indent + '\t') : indent;
const keys = Object.keys(obj);
if (keys.length === 0) {
return '';
}
let result = settings.newLineFirst ? '\n' : '';
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (i === 0 && !settings.indentFirstObject) {
result += indent + key + ': ' + stringifyObject(obj[key], newIndent, stringifyLiteral, settings);
} else {
result += newIndent + key + ': ' + stringifyObject(obj[key], newIndent, stringifyLiteral, settings);

/**
* When we are autocompleting a snippet from a property we need the indent so everything underneath the property
* is propertly indented. When we are auto completion from a value we don't want the indent because the cursor
* is already in the correct place
*/
let newIndent = ((depth === 0 && settings.shouldIndentWithTab) || depth > 0) ? (indent + ' ') : '';
if (Array.isArray(obj)) {
if (obj.length === 0) {
return '';
}
let result = ((depth === 0 && settings.newLineFirst) || depth > 0) ? '\n' : '';
for (let i = 0; i < obj.length; i++) {
result += newIndent + stringifyObject(obj[i], indent, stringifyLiteral, settings, depth += 1);
if (i < obj.length - 1) {
result += '\n';
}
}
result += indent;
return result;
} else {
let keys = Object.keys(obj);
if (keys.length === 0) {
return '';
}
let result = ((depth === 0 && settings.newLineFirst) || depth > 0) ? '\n' : '';
for (let i = 0; i < keys.length; i++) {
let key = keys[i];

// The first child of an array needs to be treated specially, otherwise identations will be off
if (depth === 0 && i === 0 && !settings.indentFirstObject) {
result += indent + key + ': ' + stringifyObject(obj[key], newIndent, stringifyLiteral, settings, depth += 1);
} else {
result += newIndent + key + ': ' + stringifyObject(obj[key], newIndent, stringifyLiteral, settings, depth += 1);
}
if (i < keys.length - 1) {
result += '\n';
}
}
result += '\n';
result += indent;
return result;
}
result += indent;
return result;
}
return stringifyLiteral(obj);
}
34 changes: 28 additions & 6 deletions test/defaultSnippets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ suite('Default Snippet Tests', () => {
const completion = parseSetup(content, 11);
completion.then(function (result) {
assert.equal(result.items.length, 1);
assert.equal(result.items[0].insertText, 'item1: $1\n\titem2: $2\n');
assert.equal(result.items[0].insertText, 'item1: $1\n item2: $2');
assert.equal(result.items[0].label, 'My array item');
}).then(done, done);
});
Expand All @@ -48,17 +48,17 @@ suite('Default Snippet Tests', () => {
const completion = parseSetup(content, 24);
completion.then(function (result) {
assert.equal(result.items.length, 1);
assert.equal(result.items[0].insertText, 'item1: $1\n\titem2: $2\n');
assert.equal(result.items[0].insertText, 'item1: $1\n item2: $2');
assert.equal(result.items[0].label, 'My array item');
}).then(done, done);
});

it('Snippet in array schema should autocomplete correctly after ', done => {
const content = 'array:\n - item1: asd\n - item2: asd\n ';
const content = 'array:\n - item1: asd\n item2: asd\n ';
const completion = parseSetup(content, 40);
completion.then(function (result) {
assert.equal(result.items.length, 1);
assert.equal(result.items[0].insertText, 'item1: $1\nitem2: $2\n');
assert.equal(result.items[0].insertText, 'item1: $1\nitem2: $2');
assert.equal(result.items[0].label, 'My array item');
}).then(done, done);
});
Expand All @@ -76,7 +76,7 @@ suite('Default Snippet Tests', () => {
const completion = parseSetup(content, 11);
completion.then(function (result) {
assert.equal(result.items.length, 2);
assert.equal(result.items[0].insertText, 'key1: $1\nkey2: $2\n');
assert.equal(result.items[0].insertText, 'key1: $1\nkey2: $2');
assert.equal(result.items[0].label, 'Object item');
assert.equal(result.items[1].insertText, 'key:\n\t$1');
assert.equal(result.items[1].label, 'key');
Expand All @@ -88,7 +88,7 @@ suite('Default Snippet Tests', () => {
const completion = parseSetup(content, 20);
completion.then(function (result) {
assert.notEqual(result.items.length, 0);
assert.equal(result.items[0].insertText, 'key1: $1\nkey2: $2\n');
assert.equal(result.items[0].insertText, 'key1: $1\nkey2: $2');
assert.equal(result.items[0].label, 'Object item');
assert.equal(result.items[1].insertText, 'key:\n\t$1');
assert.equal(result.items[1].label, 'key');
Expand Down Expand Up @@ -122,5 +122,27 @@ suite('Default Snippet Tests', () => {
assert.equal(result.items[0].insertText, 'false');
}).then(done, done);
});

it('Snippet in boolean schema should autocomplete on same line', done => {
const content = 'longSnippet: ';
const completion = parseSetup(content, 13);
completion.then(function (result) {
assert.equal(result.items.length, 1);
assert.equal(result.items[0].label, 'apply-manifests');
// tslint:disable-next-line:max-line-length
assert.equal(result.items[0].insertText, '\n name: $1\n taskRef: \n name: apply-manifests \n resources: \n inputs: \n \n name: source\n resource: $3 \n params: \n \n name: manifest_dir\n value: $2 ');
}).then(done, done);
});

it('Snippet in boolean schema should autocomplete on same line', done => {
const content = 'lon ';
const completion = parseSetup(content, 3);
completion.then(function (result) {
assert.equal(result.items.length, 5);
assert.equal(result.items[4].label, 'longSnippet');
// tslint:disable-next-line:max-line-length
assert.equal(result.items[4].insertText, 'longSnippet:\n name: $1\n taskRef: \n name: apply-manifests \n resources: \n inputs: \n \n name: source\n resource: $3 \n params: \n \n name: manifest_dir\n value: $2 ');
}).then(done, done);
});
});
});
29 changes: 29 additions & 0 deletions test/fixtures/defaultSnippets.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,35 @@
"bodyText": "false"
}
]
},
"longSnippet": {
"type": "object",
"defaultSnippets": [
{
"label": "apply-manifests",
"description": "Task",
"body": {
"name": "$1",
"taskRef": {
"name": "apply-manifests"
},
"resources": {
"inputs": [
{
"name": "source",
"resource": "$3"
}
]
},
"params": [
{
"name": "manifest_dir",
"value": "$2"
}
]
}
}
]
}
}
}

0 comments on commit 3d79d59

Please sign in to comment.