Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
131 changes: 96 additions & 35 deletions src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { indexOf, isInComment, isMapContainsEmptyPair } from '../utils/astUtils'
import { isModeline } from './modelineUtil';
import { getSchemaTypeName, isAnyOfAllOfOneOfType, isPrimitiveType } from '../utils/schemaUtils';
import { YamlNode } from '../jsonASTTypes';
import { addIndentationToMultilineString } from '../utils/strings';

const localize = nls.loadMessageBundle();

Expand All @@ -62,6 +63,20 @@ interface CompletionsCollector {
getNumberOfProposals(): number;
result: CompletionList;
proposed: { [key: string]: CompletionItem };
context: {
/**
* The content of the line where the completion is happening.
*/
lineContent?: string;
/**
* `true` if the line has a colon.
*/
hasColon?: boolean;
/**
* `true` if the line starts with a hyphen.
*/
hasHyphen?: boolean;
};
}

interface InsertText {
Expand Down Expand Up @@ -459,6 +474,7 @@ export class YamlCompletion {
},
result,
proposed,
context: {},
};

if (this.customTags && this.customTags.length > 0) {
Expand Down Expand Up @@ -670,6 +686,10 @@ export class YamlCompletion {
}
}

collector.context.lineContent = lineContent;
collector.context.hasColon = lineContent.indexOf(':') !== -1;
collector.context.hasHyphen = lineContent.trimStart().indexOf('-') === 0;

// completion for object keys
if (node && isMap(node)) {
// don't suggest properties that are already present
Expand Down Expand Up @@ -698,7 +718,7 @@ export class YamlCompletion {
collector.add({
kind: CompletionItemKind.Property,
label: currentWord,
insertText: this.getInsertTextForProperty(currentWord, null, ''),
insertText: this.getInsertTextForProperty(currentWord, null, '', collector),
insertTextFormat: InsertTextFormat.Snippet,
});
}
Expand Down Expand Up @@ -940,6 +960,7 @@ export class YamlCompletion {
key,
propertySchema,
separatorAfter,
collector,
indentCompensation + this.indentation
);
}
Expand Down Expand Up @@ -970,6 +991,7 @@ export class YamlCompletion {
key,
propertySchema,
separatorAfter,
collector,
indentCompensation + this.indentation
),
insertTextFormat: InsertTextFormat.Snippet,
Expand Down Expand Up @@ -1127,7 +1149,7 @@ export class YamlCompletion {
index?: number
): void {
const schemaType = getSchemaTypeName(schema);
const insertText = `- ${this.getInsertTextForObject(schema, separatorAfter).insertText.trimLeft()}`;
const insertText = `- ${this.getInsertTextForObject(schema, separatorAfter, collector).insertText.trimLeft()}`;
//append insertText to documentation
const schemaTypeTitle = schemaType ? ' type `' + schemaType + '`' : '';
const schemaDescription = schema.description ? ' (' + schema.description + ')' : '';
Expand All @@ -1148,6 +1170,7 @@ export class YamlCompletion {
key: string,
propertySchema: JSONSchema,
separatorAfter: string,
collector: CompletionsCollector,
indent = this.indentation
): string {
const propertyText = this.getInsertTextForValue(key, '', 'string');
Expand Down Expand Up @@ -1218,11 +1241,11 @@ export class YamlCompletion {
nValueProposals += propertySchema.examples.length;
}
if (propertySchema.properties) {
return `${resultText}\n${this.getInsertTextForObject(propertySchema, separatorAfter, indent).insertText}`;
return `${resultText}\n${this.getInsertTextForObject(propertySchema, separatorAfter, collector, indent).insertText}`;
} else if (propertySchema.items) {
return `${resultText}\n${indent}- ${
this.getInsertTextForArray(propertySchema.items, separatorAfter, 1, indent).insertText
}`;
let insertText = this.getInsertTextForArray(propertySchema.items, separatorAfter, collector, 1, indent).insertText;
insertText = resultText + addIndentationToMultilineString(insertText, `\n${indent}- `, ' ');
return insertText;
}
if (nValueProposals === 0) {
switch (type) {
Expand Down Expand Up @@ -1262,10 +1285,30 @@ export class YamlCompletion {
private getInsertTextForObject(
schema: JSONSchema,
separatorAfter: string,
collector: CompletionsCollector,
indent = this.indentation,
insertIndex = 1
): InsertText {
let insertText = '';
if (Array.isArray(schema.defaultSnippets) && schema.defaultSnippets.length === 1) {
const body = schema.defaultSnippets[0].body;
if (isDefined(body)) {
let value = this.getInsertTextForSnippetValue(
body,
'',
{
newLineFirst: false,
indentFirstObject: false,
shouldIndentWithTab: false,
},
[],
0
);
value = addIndentationToMultilineString(value, indent, indent);

return { insertText: value, insertIndex };
}
}
if (!schema.properties) {
insertText = `${indent}$${insertIndex++}\n`;
return { insertText, insertIndex };
Expand Down Expand Up @@ -1306,25 +1349,30 @@ export class YamlCompletion {
}
case 'array':
{
const arrayInsertResult = this.getInsertTextForArray(propertySchema.items, separatorAfter, insertIndex++, indent);
const arrayInsertLines = arrayInsertResult.insertText.split('\n');
let arrayTemplate = arrayInsertResult.insertText;
if (arrayInsertLines.length > 1) {
for (let index = 1; index < arrayInsertLines.length; index++) {
const element = arrayInsertLines[index];
arrayInsertLines[index] = ` ${element}`;
}
arrayTemplate = arrayInsertLines.join('\n');
}
const arrayInsertResult = this.getInsertTextForArray(
propertySchema.items,
separatorAfter,
collector,
insertIndex++,
indent
);

insertIndex = arrayInsertResult.insertIndex;
insertText += `${indent}${keyEscaped}:\n${indent}${this.indentation}- ${arrayTemplate}\n`;
insertText +=
`${indent}${keyEscaped}:` +
addIndentationToMultilineString(
arrayInsertResult.insertText,
`\n${indent}${this.indentation}- `,
`${this.indentation} `
);
}
break;
case 'object':
{
const objectInsertResult = this.getInsertTextForObject(
propertySchema,
separatorAfter,
collector,
`${indent}${this.indentation}`,
insertIndex++
);
Expand Down Expand Up @@ -1360,8 +1408,14 @@ export class YamlCompletion {
return { insertText, insertIndex };
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private getInsertTextForArray(schema: any, separatorAfter: string, insertIndex = 1, indent = this.indentation): InsertText {
private getInsertTextForArray(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
schema: any,
separatorAfter: string,
collector: CompletionsCollector,
insertIndex = 1,
indent = this.indentation
): InsertText {
let insertText = '';
if (!schema) {
insertText = `$${insertIndex++}`;
Expand Down Expand Up @@ -1389,7 +1443,7 @@ export class YamlCompletion {
break;
case 'object':
{
const objectInsertResult = this.getInsertTextForObject(schema, separatorAfter, `${indent} `, insertIndex++);
const objectInsertResult = this.getInsertTextForObject(schema, separatorAfter, collector, indent, insertIndex++);
insertText = objectInsertResult.insertText.trimLeft();
insertIndex = objectInsertResult.insertIndex;
}
Expand Down Expand Up @@ -1591,11 +1645,11 @@ export class YamlCompletion {
indentFirstObject: !isArray,
shouldIndentWithTab: !isArray,
},
0,
arrayDepth,
isArray
);
if (!hasProposals && typeof schema.items === 'object' && !Array.isArray(schema.items)) {
this.addDefaultValueCompletions(schema.items, separatorAfter, collector, arrayDepth + 1);
this.addDefaultValueCompletions(schema.items, separatorAfter, collector, arrayDepth + 1, true);
}
}

Expand Down Expand Up @@ -1656,24 +1710,13 @@ export class YamlCompletion {
if (Array.isArray(schema.defaultSnippets)) {
for (const s of schema.defaultSnippets) {
let type = schema.type;
let value = s.body;
const value = s.body;
let label = s.label;
let insertText: string;
let filterText: string;
if (isDefined(value)) {
const type = s.type || schema.type;
if ((arrayDepth === 0 && type === 'array') || isArray) {
// We know that a - isn't present yet so we need to add one
const fixedObj = {};
Object.keys(value).forEach((val, index) => {
if (index === 0 && !val.startsWith('-')) {
fixedObj[`- ${val}`] = value[val];
} else {
fixedObj[` ${val}`] = value[val];
}
});
value = fixedObj;
}

const existingProps = Object.keys(collector.proposed).filter(
(proposedProp) => collector.proposed[proposedProp].label === existingProposeItem
);
Expand All @@ -1683,6 +1726,24 @@ export class YamlCompletion {
if (insertText === '' && value) {
continue;
}

if ((arrayDepth === 0 && type === 'array') || isArray) {
// add extra hyphen if we are in array, but the hyphen is missing on current line
// but don't add it for array value because it's already there from getInsertTextForSnippetValue
const addHyphen = !collector.context.hasHyphen && !Array.isArray(value) ? '- ' : '';
// add new line if the cursor is after the colon
const addNewLine = collector.context.hasColon ? `\n${this.indentation}` : '';
// add extra indent if new line and hyphen are added
const addIndent = isArray && addNewLine && addHyphen ? this.indentation : '';
// const addIndent = addHyphen && addNewLine ? this.indentation : '';

insertText = addIndentationToMultilineString(
insertText.trimStart(),
`${addNewLine}${addHyphen}`,
`${addIndent}${this.indentation}`
);
}

label = label || this.getLabelForSnippetValue(value);
} else if (typeof s.bodyText === 'string') {
let prefix = '',
Expand Down
6 changes: 5 additions & 1 deletion src/languageservice/services/yamlHoverDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ export class YamlHoverDetail {
private schema2Md = new Schema2Md();
propTableStyle: YamlHoverDetailPropTableStyle;

constructor(schemaService: YAMLSchemaService, private readonly telemetry: Telemetry) {
// eslint-disable-next-line prettier/prettier
constructor(
schemaService: YAMLSchemaService,
private readonly telemetry: Telemetry
) {
// this.shouldHover = true;
this.schemaService = schemaService;
}
Expand Down
2 changes: 1 addition & 1 deletion src/languageservice/utils/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function stringifyObject(
let result = '';
for (let i = 0; i < obj.length; i++) {
let pseudoObj = obj[i];
if (typeof obj[i] !== 'object') {
if (typeof obj[i] !== 'object' || obj[i] === null) {
result += '\n' + newIndent + '- ' + stringifyLiteral(obj[i]);
continue;
}
Expand Down
15 changes: 15 additions & 0 deletions src/languageservice/utils/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,18 @@ export function getFirstNonWhitespaceCharacterAfterOffset(str: string, offset: n
}
return offset;
}

export function addIndentationToMultilineString(text: string, firstIndent: string, nextIndent: string): string {
let wasFirstLineIndented = false;
return text.replace(/^.*$/gm, (match) => {
if (!match) {
return match;
}
// Add fistIndent to first line or if the previous line was empty
if (!wasFirstLineIndented) {
wasFirstLineIndented = true;
return firstIndent + match;
}
return nextIndent + match; // Add indent to other lines
});
}
Loading