alm
Version:
The best IDE for TypeScript
477 lines (476 loc) • 20.9 kB
JavaScript
/**
* Based on https://github.com/Microsoft/vscode-json-languageservice/blob/master/src/services/jsonCompletion.ts
*
* - Redirected dependencies
* - I deleted stuff around `jsonContributions` (I have no idea what it is)
* - Changed TextModel => monaco.IModel
* - Removed any constructor parameters
*/
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
;
Object.defineProperty(exports, "__esModule", { value: true });
/** BAS the function that was provided by vscode-nls */
var localize_1 = require("../core/localize");
var schemaService = require("./jsonSchemaService");
var CompletionItemKind = monaco.languages.CompletionItemKind;
var Range = monaco.Range;
/**
* BAS: Was simple so I wrote it :)
*/
var TextEdit;
(function (TextEdit) {
function replace(range, text) {
var result = {
range: range,
text: text
};
return result;
}
TextEdit.replace = replace;
})(TextEdit || (TextEdit = {}));
var JSONCompletion = /** @class */ (function () {
function JSONCompletion() {
this.promise = Promise;
}
JSONCompletion.prototype.doComplete = function (document, position, doc) {
var _this = this;
var offset = document.getOffsetAt(position);
var node = doc.getNodeFromOffsetEndInclusive(offset);
var currentWord = this.getCurrentWord(document, offset);
var overwriteRange = null;
var filterText = void 0;
var result = {
items: [],
isIncomplete: false
};
if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
var start = document.getPositionAt(node.start);
var end = document.getPositionAt(node.end);
overwriteRange = new Range(start.lineNumber, start.column, end.lineNumber, end.column);
filterText = document.getValue().substring(node.start, offset);
}
else {
var start = document.getPositionAt(offset - currentWord.length);
var end = position;
overwriteRange = new Range(start.lineNumber, start.column, end.lineNumber, end.column);
filterText = document.getValue().substring(offset - currentWord.length, offset);
}
var proposed = {};
var collector = {
add: function (suggestion) {
if (!proposed[suggestion.label]) {
proposed[suggestion.label] = true;
if (overwriteRange) {
suggestion.textEdit = TextEdit.replace(overwriteRange, suggestion.insertText.toString());
suggestion.filterText = filterText;
}
result.items.push(suggestion);
}
},
setAsIncomplete: function () {
result.isIncomplete = true;
},
error: function (message) {
console.error(message);
},
log: function (message) {
console.log(message);
}
};
return schemaService.getSchemaForResource(document.filePath).then(function (schema) {
var collectionPromises = [];
var addValue = true;
var currentKey = '';
var currentProperty = null;
if (node) {
if (node.type === 'string') {
var stringNode = node;
if (stringNode.isKey) {
addValue = !(node.parent && (node.parent.value));
currentProperty = node.parent ? node.parent : null;
currentKey = document.getValue().substring(node.start + 1, node.end - 1);
if (node.parent) {
node = 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.start === offset) {
return result;
}
// don't suggest properties that are already present
var properties = node.properties;
properties.forEach(function (p) {
if (!currentProperty || currentProperty !== p) {
proposed[p.key.value] = true;
}
});
var isLast = properties.length === 0 || offset >= properties[properties.length - 1].start;
if (schema) {
// property proposals with schema
_this.getPropertySuggestions(schema, doc, node, addValue, isLast, collector);
}
else {
// property proposals without schema
_this.getSchemaLessPropertySuggestions(doc, node, currentKey, currentWord, isLast, collector);
}
}
// proposals for values
if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
node = node.parent;
}
if (schema) {
// value proposals with schema
_this.getValueSuggestions(schema, doc, node, offset, collector);
}
else {
// value proposals without schema
_this.getSchemaLessValueSuggestions(doc, node, offset, document, collector);
}
if (!node) {
}
else {
if ((node.type === 'property') && offset > node.colonOffset) {
var parentKey = node.key.value;
var valueNode = node.value;
if (!valueNode || offset <= valueNode.end) {
}
}
}
return _this.promise.all(collectionPromises).then(function () { return result; });
});
};
JSONCompletion.prototype.getPropertySuggestions = function (schema, doc, node, addValue, isLast, collector) {
var _this = this;
var matchingSchemas = [];
doc.validate(schema.schema, matchingSchemas, node.start);
matchingSchemas.forEach(function (s) {
if (s.node === node && !s.inverted) {
var schemaProperties_1 = s.schema.properties;
if (schemaProperties_1) {
Object.keys(schemaProperties_1).forEach(function (key) {
var propertySchema = schemaProperties_1[key];
collector.add({ kind: CompletionItemKind.Property, label: key, insertText: _this.getTextForProperty(key, propertySchema, addValue, isLast), documentation: propertySchema.description || '' });
});
}
}
});
};
JSONCompletion.prototype.getSchemaLessPropertySuggestions = function (doc, node, currentKey, currentWord, isLast, collector) {
var _this = this;
var collectSuggestionsForSimilarObject = function (obj) {
obj.properties.forEach(function (p) {
var key = p.key.value;
collector.add({ kind: CompletionItemKind.Property, label: key, insertText: _this.getTextForSimilarProperty(key, p.value), documentation: '' });
});
};
if (node.parent) {
if (node.parent.type === 'property') {
// if the object is a property value, check the tree for other objects that hang under a property of the same name
var parentKey_1 = node.parent.key.value;
doc.visit(function (n) {
if (n.type === 'property' && n.key.value === parentKey_1 && n.value && n.value.type === 'object') {
collectSuggestionsForSimilarObject(n.value);
}
return true;
});
}
else if (node.parent.type === 'array') {
// if the object is in an array, use all other array elements as similar objects
node.parent.items.forEach(function (n) {
if (n.type === 'object' && n !== node) {
collectSuggestionsForSimilarObject(n);
}
});
}
}
if (!currentKey && currentWord.length > 0) {
collector.add({ kind: CompletionItemKind.Property, label: this.getLabelForValue(currentWord), insertText: this.getTextForProperty(currentWord, null, true, isLast), documentation: '' });
}
};
JSONCompletion.prototype.getSchemaLessValueSuggestions = function (doc, node, offset, document, collector) {
var _this = this;
var collectSuggestionsForValues = function (value) {
if (!value.contains(offset)) {
var content = _this.getTextForMatchingNode(value, document);
collector.add({ kind: _this.getSuggestionKind(value.type), label: content, insertText: content, documentation: '' });
}
if (value.type === 'boolean') {
_this.addBooleanSuggestion(!value.getValue(), collector);
}
};
if (!node) {
collector.add({ kind: this.getSuggestionKind('object'), label: 'Empty object', insertText: '{\n\t{{}}\n}', documentation: '' });
collector.add({ kind: this.getSuggestionKind('array'), label: 'Empty array', insertText: '[\n\t{{}}\n]', documentation: '' });
}
else {
if (node.type === 'property' && offset > node.colonOffset) {
var valueNode = node.value;
if (valueNode && offset > valueNode.end) {
return;
}
// suggest values at the same key
var parentKey_2 = node.key.value;
doc.visit(function (n) {
if (n.type === 'property' && n.key.value === parentKey_2 && n.value) {
collectSuggestionsForValues(n.value);
}
return true;
});
}
if (node.type === 'array') {
if (node.parent && node.parent.type === 'property') {
// suggest items of an array at the same key
var parentKey_3 = node.parent.key.value;
doc.visit(function (n) {
if (n.type === 'property' && n.key.value === parentKey_3 && n.value && n.value.type === 'array') {
(n.value.items).forEach(function (n) {
collectSuggestionsForValues(n);
});
}
return true;
});
}
else {
// suggest items in the same array
node.items.forEach(function (n) {
collectSuggestionsForValues(n);
});
}
}
}
};
JSONCompletion.prototype.getValueSuggestions = function (schema, doc, node, offset, collector) {
var _this = this;
if (!node) {
this.addDefaultSuggestion(schema.schema, collector);
}
else {
var parentKey_4 = null;
if (node && (node.type === 'property') && offset > node.colonOffset) {
var valueNode = node.value;
if (valueNode && offset > valueNode.end) {
return; // we are past the value node
}
parentKey_4 = node.key.value;
node = node.parent;
}
if (node && (parentKey_4 !== null || node.type === 'array')) {
var matchingSchemas = [];
doc.validate(schema.schema, matchingSchemas, node.start);
matchingSchemas.forEach(function (s) {
if (s.node === node && !s.inverted && s.schema) {
if (s.schema.items) {
_this.addDefaultSuggestion(s.schema.items, collector);
_this.addEnumSuggestion(s.schema.items, collector);
}
if (s.schema.properties) {
var propertySchema = s.schema.properties[parentKey_4];
if (propertySchema) {
_this.addDefaultSuggestion(propertySchema, collector);
_this.addEnumSuggestion(propertySchema, collector);
}
}
}
});
}
}
};
JSONCompletion.prototype.addBooleanSuggestion = function (value, collector) {
collector.add({ kind: this.getSuggestionKind('boolean'), label: value ? 'true' : 'false', insertText: this.getTextForValue(value), documentation: '' });
};
JSONCompletion.prototype.addNullSuggestion = function (collector) {
collector.add({ kind: this.getSuggestionKind('null'), label: 'null', insertText: 'null', documentation: '' });
};
JSONCompletion.prototype.addEnumSuggestion = function (schema, collector) {
var _this = this;
if (Array.isArray(schema.enum)) {
schema.enum.forEach(function (enm) { return collector.add({ kind: _this.getSuggestionKind(schema.type), label: _this.getLabelForValue(enm), insertText: _this.getTextForValue(enm), documentation: '' }); });
}
else {
if (this.isType(schema, 'boolean')) {
this.addBooleanSuggestion(true, collector);
this.addBooleanSuggestion(false, collector);
}
if (this.isType(schema, 'null')) {
this.addNullSuggestion(collector);
}
}
if (Array.isArray(schema.allOf)) {
schema.allOf.forEach(function (s) { return _this.addEnumSuggestion(s, collector); });
}
if (Array.isArray(schema.anyOf)) {
schema.anyOf.forEach(function (s) { return _this.addEnumSuggestion(s, collector); });
}
if (Array.isArray(schema.oneOf)) {
schema.oneOf.forEach(function (s) { return _this.addEnumSuggestion(s, collector); });
}
};
JSONCompletion.prototype.isType = function (schema, type) {
if (Array.isArray(schema.type)) {
return schema.type.indexOf(type) !== -1;
}
return schema.type === type;
};
JSONCompletion.prototype.addDefaultSuggestion = function (schema, collector) {
var _this = this;
if (schema.default) {
collector.add({
kind: this.getSuggestionKind(schema.type),
label: this.getLabelForValue(schema.default),
insertText: this.getTextForValue(schema.default),
detail: localize_1.localize('json.suggest.default', 'Default value'),
});
}
if (Array.isArray(schema.defaultSnippets)) {
schema.defaultSnippets.forEach(function (s) {
collector.add({
kind: CompletionItemKind.Snippet,
label: _this.getLabelForSnippetValue(s.body),
insertText: _this.getTextForSnippetValue(s.body)
});
});
}
if (Array.isArray(schema.allOf)) {
schema.allOf.forEach(function (s) { return _this.addDefaultSuggestion(s, collector); });
}
if (Array.isArray(schema.anyOf)) {
schema.anyOf.forEach(function (s) { return _this.addDefaultSuggestion(s, collector); });
}
if (Array.isArray(schema.oneOf)) {
schema.oneOf.forEach(function (s) { return _this.addDefaultSuggestion(s, collector); });
}
};
JSONCompletion.prototype.getLabelForValue = function (value) {
var label = JSON.stringify(value);
if (label.length > 57) {
return label.substr(0, 57).trim() + '...';
}
return label;
};
JSONCompletion.prototype.getLabelForSnippetValue = function (value) {
var label = JSON.stringify(value);
label = label.replace(/\{\{|\}\}/g, '');
if (label.length > 57) {
return label.substr(0, 57).trim() + '...';
}
return label;
};
JSONCompletion.prototype.getTextForValue = function (value) {
var text = JSON.stringify(value, null, '\t');
text = text.replace(/[\\\{\}]/g, '\\$&');
return text;
};
JSONCompletion.prototype.getTextForSnippetValue = function (value) {
return JSON.stringify(value, null, '\t');
};
JSONCompletion.prototype.getTextForEnumValue = function (value) {
var snippet = this.getTextForValue(value);
switch (typeof value) {
case 'object':
if (value === null) {
return '{{null}}';
}
return snippet;
case 'string':
return '"{{' + snippet.substr(1, snippet.length - 2) + '}}"';
case 'number':
case 'boolean':
return '{{' + snippet + '}}';
}
return snippet;
};
JSONCompletion.prototype.getSuggestionKind = function (type) {
if (Array.isArray(type)) {
var array = type;
type = array.length > 0 ? array[0] : null;
}
if (!type) {
return CompletionItemKind.Value;
}
switch (type) {
case 'string': return CompletionItemKind.Value;
case 'object': return CompletionItemKind.Module;
case 'property': return CompletionItemKind.Property;
default: return CompletionItemKind.Value;
}
};
JSONCompletion.prototype.getTextForMatchingNode = function (node, document) {
switch (node.type) {
case 'array':
return '[]';
case 'object':
return '{}';
default:
var content = document.getValue().substr(node.start, node.end - node.start);
return content;
}
};
JSONCompletion.prototype.getTextForProperty = function (key, propertySchema, addValue, isLast) {
var result = this.getTextForValue(key);
if (!addValue) {
return result;
}
result += ': ';
if (propertySchema) {
var defaultVal = propertySchema.default;
if (typeof defaultVal !== 'undefined') {
result = result + this.getTextForEnumValue(defaultVal);
}
else if (propertySchema.enum && propertySchema.enum.length > 0) {
result = result + this.getTextForEnumValue(propertySchema.enum[0]);
}
else {
var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
switch (type) {
case 'boolean':
result += '{{false}}';
break;
case 'string':
result += '"{{}}"';
break;
case 'object':
result += '{\n\t{{}}\n}';
break;
case 'array':
result += '[\n\t{{}}\n]';
break;
case 'number':
result += '{{0}}';
break;
case 'null':
result += '{{null}}';
break;
default:
return result;
}
}
}
else {
result += '{{0}}';
}
if (!isLast) {
result += ',';
}
return result;
};
JSONCompletion.prototype.getTextForSimilarProperty = function (key, templateValue) {
return this.getTextForValue(key);
};
JSONCompletion.prototype.getCurrentWord = function (document, offset) {
var i = offset - 1;
var text = document.getValue();
while (i >= 0 && ' \t\n\r\v":{[,'.indexOf(text.charAt(i)) === -1) {
i--;
}
return text.substring(i + 1, offset);
};
return JSONCompletion;
}());
exports.JSONCompletion = JSONCompletion;