in src/services/jsonCompletion.ts [52:225]
public doComplete(document: TextDocument, position: Position, doc: Parser.JSONDocument): Thenable<CompletionList> {
const result: CompletionList = {
items: [],
isIncomplete: false
};
const text = document.getText();
const offset = document.offsetAt(position);
let node = doc.getNodeFromOffset(offset, true);
if (this.isInComment(document, node ? node.offset : 0, offset)) {
return Promise.resolve(result);
}
if (node && (offset === node.offset + node.length) && offset > 0) {
const ch = text[offset - 1];
if (node.type === 'object' && ch === '}' || node.type === 'array' && ch === ']') {
// after ] or }
node = node.parent;
}
}
const currentWord = this.getCurrentWord(document, offset);
let overwriteRange: Range;
if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
overwriteRange = Range.create(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
} else {
let overwriteStart = offset - currentWord.length;
if (overwriteStart > 0 && text[overwriteStart - 1] === '"') {
overwriteStart--;
}
overwriteRange = Range.create(document.positionAt(overwriteStart), position);
}
const supportsCommitCharacters = false; //this.doesSupportsCommitCharacters(); disabled for now, waiting for new API: https://github.com/microsoft/vscode/issues/42544
const proposed: { [key: string]: CompletionItem } = {};
const collector: CompletionsCollector = {
add: (suggestion: CompletionItem) => {
let label = suggestion.label;
const existing = proposed[label];
if (!existing) {
label = label.replace(/[\n]/g, '↵');
if (label.length > 60) {
const shortendedLabel = label.substr(0, 57).trim() + '...';
if (!proposed[shortendedLabel]) {
label = shortendedLabel;
}
}
if (overwriteRange && suggestion.insertText !== undefined) {
suggestion.textEdit = TextEdit.replace(overwriteRange, suggestion.insertText);
}
if (supportsCommitCharacters) {
suggestion.commitCharacters = suggestion.kind === CompletionItemKind.Property ? propertyCommitCharacters : valueCommitCharacters;
}
suggestion.label = label;
proposed[label] = suggestion;
result.items.push(suggestion);
} else {
if (!existing.documentation) {
existing.documentation = suggestion.documentation;
}
if (!existing.detail) {
existing.detail = suggestion.detail;
}
}
},
setAsIncomplete: () => {
result.isIncomplete = true;
},
error: (message: string) => {
console.error(message);
},
log: (message: string) => {
console.log(message);
},
getNumberOfProposals: () => {
return result.items.length;
}
};
return this.schemaService.getSchemaForResource(document.uri, doc).then((schema) => {
const collectionPromises: Thenable<any>[] = [];
let addValue = true;
let currentKey = '';
let currentProperty: PropertyASTNode | undefined = undefined;
if (node) {
if (node.type === 'string') {
const parent = node.parent;
if (parent && parent.type === 'property' && parent.keyNode === node) {
addValue = !parent.valueNode;
currentProperty = parent;
currentKey = text.substr(node.offset + 1, node.length - 2);
if (parent) {
node = parent.parent;
}
}
}
}
// proposals for properties
if (node && node.type === 'object') {
// don't suggest keys when the cursor is just before the opening curly brace
if (node.offset === offset) {
return result;
}
// don't suggest properties that are already present
const properties = node.properties;
properties.forEach(p => {
if (!currentProperty || currentProperty !== p) {
proposed[p.keyNode.value] = CompletionItem.create('__');
}
});
let separatorAfter = '';
if (addValue) {
separatorAfter = this.evaluateSeparatorAfter(document, document.offsetAt(overwriteRange.end));
}
if (schema) {
// property proposals with schema
this.getPropertyCompletions(schema, doc, node, addValue, separatorAfter, collector);
} else {
// property proposals without schema
this.getSchemaLessPropertyCompletions(doc, node, currentKey, collector);
}
const location = Parser.getNodePath(node);
this.contributions.forEach((contribution) => {
const collectPromise = contribution.collectPropertyCompletions(document.uri, location, currentWord, addValue, separatorAfter === '', collector);
if (collectPromise) {
collectionPromises.push(collectPromise);
}
});
if ((!schema && currentWord.length > 0 && text.charAt(offset - currentWord.length - 1) !== '"')) {
collector.add({
kind: CompletionItemKind.Property,
label: this.getLabelForValue(currentWord),
insertText: this.getInsertTextForProperty(currentWord, undefined, false, separatorAfter),
insertTextFormat: InsertTextFormat.Snippet, documentation: '',
});
collector.setAsIncomplete();
}
}
// proposals for values
const types: { [type: string]: boolean } = {};
if (schema) {
// value proposals with schema
this.getValueCompletions(schema, doc, node, offset, document, collector, types);
} else {
// value proposals without schema
this.getSchemaLessValueCompletions(doc, node, offset, document, collector);
}
if (this.contributions.length > 0) {
this.getContributedValueCompletions(doc, node, offset, document, collector, collectionPromises);
}
return this.promiseConstructor.all(collectionPromises).then(() => {
if (collector.getNumberOfProposals() === 0) {
let offsetForSeparator = offset;
if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
offsetForSeparator = node.offset + node.length;
}
const separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
this.addFillerValueCompletions(types, separatorAfter, collector);
}
return result;
});
});
}