--- /dev/null
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+(function (factory) {
+ if (typeof module === "object" && typeof module.exports === "object") {
+ var v = factory(require, exports);
+ if (v !== undefined) module.exports = v;
+ }
+ else if (typeof define === "function" && define.amd) {
+ define(["require", "exports", "../parser/jsonParser", "jsonc-parser", "../utils/json", "../utils/strings", "../utils/objects", "../jsonLanguageTypes", "vscode-nls"], factory);
+ }
+})(function (require, exports) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.JSONCompletion = void 0;
+ var Parser = require("../parser/jsonParser");
+ var Json = require("jsonc-parser");
+ var json_1 = require("../utils/json");
+ var strings_1 = require("../utils/strings");
+ var objects_1 = require("../utils/objects");
+ var jsonLanguageTypes_1 = require("../jsonLanguageTypes");
+ var nls = require("vscode-nls");
+ var localize = nls.loadMessageBundle();
+ var valueCommitCharacters = [',', '}', ']'];
+ var propertyCommitCharacters = [':'];
+ var JSONCompletion = /** @class */ (function () {
+ function JSONCompletion(schemaService, contributions, promiseConstructor, clientCapabilities) {
+ if (contributions === void 0) { contributions = []; }
+ if (promiseConstructor === void 0) { promiseConstructor = Promise; }
+ if (clientCapabilities === void 0) { clientCapabilities = {}; }
+ this.schemaService = schemaService;
+ this.contributions = contributions;
+ this.promiseConstructor = promiseConstructor;
+ this.clientCapabilities = clientCapabilities;
+ }
+ JSONCompletion.prototype.doResolve = function (item) {
+ for (var i = this.contributions.length - 1; i >= 0; i--) {
+ var resolveCompletion = this.contributions[i].resolveCompletion;
+ if (resolveCompletion) {
+ var resolver = resolveCompletion(item);
+ if (resolver) {
+ return resolver;
+ }
+ }
+ }
+ return this.promiseConstructor.resolve(item);
+ };
+ JSONCompletion.prototype.doComplete = function (document, position, doc) {
+ var _this = this;
+ var result = {
+ items: [],
+ isIncomplete: false
+ };
+ var text = document.getText();
+ var offset = document.offsetAt(position);
+ var 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) {
+ var ch = text[offset - 1];
+ if (node.type === 'object' && ch === '}' || node.type === 'array' && ch === ']') {
+ // after ] or }
+ node = node.parent;
+ }
+ }
+ var currentWord = this.getCurrentWord(document, offset);
+ var overwriteRange;
+ if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
+ overwriteRange = jsonLanguageTypes_1.Range.create(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
+ }
+ else {
+ var overwriteStart = offset - currentWord.length;
+ if (overwriteStart > 0 && text[overwriteStart - 1] === '"') {
+ overwriteStart--;
+ }
+ overwriteRange = jsonLanguageTypes_1.Range.create(document.positionAt(overwriteStart), position);
+ }
+ var supportsCommitCharacters = false; //this.doesSupportsCommitCharacters(); disabled for now, waiting for new API: https://github.com/microsoft/vscode/issues/42544
+ var proposed = {};
+ var collector = {
+ add: function (suggestion) {
+ var label = suggestion.label;
+ var existing = proposed[label];
+ if (!existing) {
+ label = label.replace(/[\n]/g, '↵');
+ if (label.length > 60) {
+ var shortendedLabel = label.substr(0, 57).trim() + '...';
+ if (!proposed[shortendedLabel]) {
+ label = shortendedLabel;
+ }
+ }
+ if (overwriteRange && suggestion.insertText !== undefined) {
+ suggestion.textEdit = jsonLanguageTypes_1.TextEdit.replace(overwriteRange, suggestion.insertText);
+ }
+ if (supportsCommitCharacters) {
+ suggestion.commitCharacters = suggestion.kind === jsonLanguageTypes_1.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: function () {
+ result.isIncomplete = true;
+ },
+ error: function (message) {
+ console.error(message);
+ },
+ log: function (message) {
+ console.log(message);
+ },
+ getNumberOfProposals: function () {
+ return result.items.length;
+ }
+ };
+ return this.schemaService.getSchemaForResource(document.uri, doc).then(function (schema) {
+ var collectionPromises = [];
+ var addValue = true;
+ var currentKey = '';
+ var currentProperty = undefined;
+ if (node) {
+ if (node.type === 'string') {
+ var 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
+ var properties = node.properties;
+ properties.forEach(function (p) {
+ if (!currentProperty || currentProperty !== p) {
+ proposed[p.keyNode.value] = jsonLanguageTypes_1.CompletionItem.create('__');
+ }
+ });
+ var separatorAfter_1 = '';
+ if (addValue) {
+ separatorAfter_1 = _this.evaluateSeparatorAfter(document, document.offsetAt(overwriteRange.end));
+ }
+ if (schema) {
+ // property proposals with schema
+ _this.getPropertyCompletions(schema, doc, node, addValue, separatorAfter_1, collector);
+ }
+ else {
+ // property proposals without schema
+ _this.getSchemaLessPropertyCompletions(doc, node, currentKey, collector);
+ }
+ var location_1 = Parser.getNodePath(node);
+ _this.contributions.forEach(function (contribution) {
+ var collectPromise = contribution.collectPropertyCompletions(document.uri, location_1, currentWord, addValue, separatorAfter_1 === '', collector);
+ if (collectPromise) {
+ collectionPromises.push(collectPromise);
+ }
+ });
+ if ((!schema && currentWord.length > 0 && text.charAt(offset - currentWord.length - 1) !== '"')) {
+ collector.add({
+ kind: jsonLanguageTypes_1.CompletionItemKind.Property,
+ label: _this.getLabelForValue(currentWord),
+ insertText: _this.getInsertTextForProperty(currentWord, undefined, false, separatorAfter_1),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet, documentation: '',
+ });
+ collector.setAsIncomplete();
+ }
+ }
+ // proposals for values
+ var types = {};
+ 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(function () {
+ if (collector.getNumberOfProposals() === 0) {
+ var offsetForSeparator = offset;
+ if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
+ offsetForSeparator = node.offset + node.length;
+ }
+ var separatorAfter = _this.evaluateSeparatorAfter(document, offsetForSeparator);
+ _this.addFillerValueCompletions(types, separatorAfter, collector);
+ }
+ return result;
+ });
+ });
+ };
+ JSONCompletion.prototype.getPropertyCompletions = function (schema, doc, node, addValue, separatorAfter, collector) {
+ var _this = this;
+ var matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset);
+ 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];
+ if (typeof propertySchema === 'object' && !propertySchema.deprecationMessage && !propertySchema.doNotSuggest) {
+ var proposal = {
+ kind: jsonLanguageTypes_1.CompletionItemKind.Property,
+ label: key,
+ insertText: _this.getInsertTextForProperty(key, propertySchema, addValue, separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ filterText: _this.getFilterTextForValue(key),
+ documentation: _this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
+ };
+ if (propertySchema.suggestSortText !== undefined) {
+ proposal.sortText = propertySchema.suggestSortText;
+ }
+ if (proposal.insertText && strings_1.endsWith(proposal.insertText, "$1" + separatorAfter)) {
+ proposal.command = {
+ title: 'Suggest',
+ command: 'editor.action.triggerSuggest'
+ };
+ }
+ collector.add(proposal);
+ }
+ });
+ }
+ var schemaPropertyNames_1 = s.schema.propertyNames;
+ if (typeof schemaPropertyNames_1 === 'object' && !schemaPropertyNames_1.deprecationMessage && !schemaPropertyNames_1.doNotSuggest) {
+ var propertyNameCompletionItem = function (name, enumDescription) {
+ if (enumDescription === void 0) { enumDescription = undefined; }
+ var proposal = {
+ kind: jsonLanguageTypes_1.CompletionItemKind.Property,
+ label: name,
+ insertText: _this.getInsertTextForProperty(name, undefined, addValue, separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ filterText: _this.getFilterTextForValue(name),
+ documentation: enumDescription || _this.fromMarkup(schemaPropertyNames_1.markdownDescription) || schemaPropertyNames_1.description || '',
+ };
+ if (schemaPropertyNames_1.suggestSortText !== undefined) {
+ proposal.sortText = schemaPropertyNames_1.suggestSortText;
+ }
+ if (proposal.insertText && strings_1.endsWith(proposal.insertText, "$1" + separatorAfter)) {
+ proposal.command = {
+ title: 'Suggest',
+ command: 'editor.action.triggerSuggest'
+ };
+ }
+ collector.add(proposal);
+ };
+ if (schemaPropertyNames_1.enum) {
+ for (var i = 0; i < schemaPropertyNames_1.enum.length; i++) {
+ var enumDescription = undefined;
+ if (schemaPropertyNames_1.markdownEnumDescriptions && i < schemaPropertyNames_1.markdownEnumDescriptions.length) {
+ enumDescription = _this.fromMarkup(schemaPropertyNames_1.markdownEnumDescriptions[i]);
+ }
+ else if (schemaPropertyNames_1.enumDescriptions && i < schemaPropertyNames_1.enumDescriptions.length) {
+ enumDescription = schemaPropertyNames_1.enumDescriptions[i];
+ }
+ propertyNameCompletionItem(schemaPropertyNames_1.enum[i], enumDescription);
+ }
+ }
+ if (schemaPropertyNames_1.const) {
+ propertyNameCompletionItem(schemaPropertyNames_1.const);
+ }
+ }
+ }
+ });
+ };
+ JSONCompletion.prototype.getSchemaLessPropertyCompletions = function (doc, node, currentKey, collector) {
+ var _this = this;
+ var collectCompletionsForSimilarObject = function (obj) {
+ obj.properties.forEach(function (p) {
+ var key = p.keyNode.value;
+ collector.add({
+ kind: jsonLanguageTypes_1.CompletionItemKind.Property,
+ label: key,
+ insertText: _this.getInsertTextForValue(key, ''),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ filterText: _this.getFilterTextForValue(key),
+ 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.keyNode.value;
+ doc.visit(function (n) {
+ if (n.type === 'property' && n !== node.parent && n.keyNode.value === parentKey_1 && n.valueNode && n.valueNode.type === 'object') {
+ collectCompletionsForSimilarObject(n.valueNode);
+ }
+ 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) {
+ collectCompletionsForSimilarObject(n);
+ }
+ });
+ }
+ }
+ else if (node.type === 'object') {
+ collector.add({
+ kind: jsonLanguageTypes_1.CompletionItemKind.Property,
+ label: '$schema',
+ insertText: this.getInsertTextForProperty('$schema', undefined, true, ''),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet, documentation: '',
+ filterText: this.getFilterTextForValue("$schema")
+ });
+ }
+ };
+ JSONCompletion.prototype.getSchemaLessValueCompletions = function (doc, node, offset, document, collector) {
+ var _this = this;
+ var offsetForSeparator = offset;
+ if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
+ offsetForSeparator = node.offset + node.length;
+ node = node.parent;
+ }
+ if (!node) {
+ collector.add({
+ kind: this.getSuggestionKind('object'),
+ label: 'Empty object',
+ insertText: this.getInsertTextForValue({}, ''),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ documentation: ''
+ });
+ collector.add({
+ kind: this.getSuggestionKind('array'),
+ label: 'Empty array',
+ insertText: this.getInsertTextForValue([], ''),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ documentation: ''
+ });
+ return;
+ }
+ var separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
+ var collectSuggestionsForValues = function (value) {
+ if (value.parent && !Parser.contains(value.parent, offset, true)) {
+ collector.add({
+ kind: _this.getSuggestionKind(value.type),
+ label: _this.getLabelTextForMatchingNode(value, document),
+ insertText: _this.getInsertTextForMatchingNode(value, document, separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet, documentation: ''
+ });
+ }
+ if (value.type === 'boolean') {
+ _this.addBooleanValueCompletion(!value.value, separatorAfter, collector);
+ }
+ };
+ if (node.type === 'property') {
+ if (offset > (node.colonOffset || 0)) {
+ var valueNode = node.valueNode;
+ if (valueNode && (offset > (valueNode.offset + valueNode.length) || valueNode.type === 'object' || valueNode.type === 'array')) {
+ return;
+ }
+ // suggest values at the same key
+ var parentKey_2 = node.keyNode.value;
+ doc.visit(function (n) {
+ if (n.type === 'property' && n.keyNode.value === parentKey_2 && n.valueNode) {
+ collectSuggestionsForValues(n.valueNode);
+ }
+ return true;
+ });
+ if (parentKey_2 === '$schema' && node.parent && !node.parent.parent) {
+ this.addDollarSchemaCompletions(separatorAfter, collector);
+ }
+ }
+ }
+ 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.keyNode.value;
+ doc.visit(function (n) {
+ if (n.type === 'property' && n.keyNode.value === parentKey_3 && n.valueNode && n.valueNode.type === 'array') {
+ n.valueNode.items.forEach(collectSuggestionsForValues);
+ }
+ return true;
+ });
+ }
+ else {
+ // suggest items in the same array
+ node.items.forEach(collectSuggestionsForValues);
+ }
+ }
+ };
+ JSONCompletion.prototype.getValueCompletions = function (schema, doc, node, offset, document, collector, types) {
+ var offsetForSeparator = offset;
+ var parentKey = undefined;
+ var valueNode = undefined;
+ if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
+ offsetForSeparator = node.offset + node.length;
+ valueNode = node;
+ node = node.parent;
+ }
+ if (!node) {
+ this.addSchemaValueCompletions(schema.schema, '', collector, types);
+ return;
+ }
+ if ((node.type === 'property') && offset > (node.colonOffset || 0)) {
+ var valueNode_1 = node.valueNode;
+ if (valueNode_1 && offset > (valueNode_1.offset + valueNode_1.length)) {
+ return; // we are past the value node
+ }
+ parentKey = node.keyNode.value;
+ node = node.parent;
+ }
+ if (node && (parentKey !== undefined || node.type === 'array')) {
+ var separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
+ var matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset, valueNode);
+ for (var _i = 0, matchingSchemas_1 = matchingSchemas; _i < matchingSchemas_1.length; _i++) {
+ var s = matchingSchemas_1[_i];
+ if (s.node === node && !s.inverted && s.schema) {
+ if (node.type === 'array' && s.schema.items) {
+ if (Array.isArray(s.schema.items)) {
+ var index = this.findItemAtOffset(node, document, offset);
+ if (index < s.schema.items.length) {
+ this.addSchemaValueCompletions(s.schema.items[index], separatorAfter, collector, types);
+ }
+ }
+ else {
+ this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types);
+ }
+ }
+ if (parentKey !== undefined) {
+ var propertyMatched = false;
+ if (s.schema.properties) {
+ var propertySchema = s.schema.properties[parentKey];
+ if (propertySchema) {
+ propertyMatched = true;
+ this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
+ }
+ }
+ if (s.schema.patternProperties && !propertyMatched) {
+ for (var _a = 0, _b = Object.keys(s.schema.patternProperties); _a < _b.length; _a++) {
+ var pattern = _b[_a];
+ var regex = strings_1.extendedRegExp(pattern);
+ if (regex.test(parentKey)) {
+ propertyMatched = true;
+ var propertySchema = s.schema.patternProperties[pattern];
+ this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
+ }
+ }
+ }
+ if (s.schema.additionalProperties && !propertyMatched) {
+ var propertySchema = s.schema.additionalProperties;
+ this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
+ }
+ }
+ }
+ }
+ if (parentKey === '$schema' && !node.parent) {
+ this.addDollarSchemaCompletions(separatorAfter, collector);
+ }
+ if (types['boolean']) {
+ this.addBooleanValueCompletion(true, separatorAfter, collector);
+ this.addBooleanValueCompletion(false, separatorAfter, collector);
+ }
+ if (types['null']) {
+ this.addNullValueCompletion(separatorAfter, collector);
+ }
+ }
+ };
+ JSONCompletion.prototype.getContributedValueCompletions = function (doc, node, offset, document, collector, collectionPromises) {
+ if (!node) {
+ this.contributions.forEach(function (contribution) {
+ var collectPromise = contribution.collectDefaultCompletions(document.uri, collector);
+ if (collectPromise) {
+ collectionPromises.push(collectPromise);
+ }
+ });
+ }
+ else {
+ if (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null') {
+ node = node.parent;
+ }
+ if (node && (node.type === 'property') && offset > (node.colonOffset || 0)) {
+ var parentKey_4 = node.keyNode.value;
+ var valueNode = node.valueNode;
+ if ((!valueNode || offset <= (valueNode.offset + valueNode.length)) && node.parent) {
+ var location_2 = Parser.getNodePath(node.parent);
+ this.contributions.forEach(function (contribution) {
+ var collectPromise = contribution.collectValueCompletions(document.uri, location_2, parentKey_4, collector);
+ if (collectPromise) {
+ collectionPromises.push(collectPromise);
+ }
+ });
+ }
+ }
+ }
+ };
+ JSONCompletion.prototype.addSchemaValueCompletions = function (schema, separatorAfter, collector, types) {
+ var _this = this;
+ if (typeof schema === 'object') {
+ this.addEnumValueCompletions(schema, separatorAfter, collector);
+ this.addDefaultValueCompletions(schema, separatorAfter, collector);
+ this.collectTypes(schema, types);
+ if (Array.isArray(schema.allOf)) {
+ schema.allOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
+ }
+ if (Array.isArray(schema.anyOf)) {
+ schema.anyOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
+ }
+ if (Array.isArray(schema.oneOf)) {
+ schema.oneOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
+ }
+ }
+ };
+ JSONCompletion.prototype.addDefaultValueCompletions = function (schema, separatorAfter, collector, arrayDepth) {
+ var _this = this;
+ if (arrayDepth === void 0) { arrayDepth = 0; }
+ var hasProposals = false;
+ if (objects_1.isDefined(schema.default)) {
+ var type = schema.type;
+ var value = schema.default;
+ for (var i = arrayDepth; i > 0; i--) {
+ value = [value];
+ type = 'array';
+ }
+ collector.add({
+ kind: this.getSuggestionKind(type),
+ label: this.getLabelForValue(value),
+ insertText: this.getInsertTextForValue(value, separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ detail: localize('json.suggest.default', 'Default value')
+ });
+ hasProposals = true;
+ }
+ if (Array.isArray(schema.examples)) {
+ schema.examples.forEach(function (example) {
+ var type = schema.type;
+ var value = example;
+ for (var i = arrayDepth; i > 0; i--) {
+ value = [value];
+ type = 'array';
+ }
+ collector.add({
+ kind: _this.getSuggestionKind(type),
+ label: _this.getLabelForValue(value),
+ insertText: _this.getInsertTextForValue(value, separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet
+ });
+ hasProposals = true;
+ });
+ }
+ if (Array.isArray(schema.defaultSnippets)) {
+ schema.defaultSnippets.forEach(function (s) {
+ var type = schema.type;
+ var value = s.body;
+ var label = s.label;
+ var insertText;
+ var filterText;
+ if (objects_1.isDefined(value)) {
+ var type_1 = schema.type;
+ for (var i = arrayDepth; i > 0; i--) {
+ value = [value];
+ type_1 = 'array';
+ }
+ insertText = _this.getInsertTextForSnippetValue(value, separatorAfter);
+ filterText = _this.getFilterTextForSnippetValue(value);
+ label = label || _this.getLabelForSnippetValue(value);
+ }
+ else if (typeof s.bodyText === 'string') {
+ var prefix = '', suffix = '', indent = '';
+ for (var 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
+ }
+ else {
+ return;
+ }
+ collector.add({
+ kind: _this.getSuggestionKind(type),
+ label: label,
+ documentation: _this.fromMarkup(s.markdownDescription) || s.description,
+ insertText: insertText,
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ filterText: filterText
+ });
+ hasProposals = true;
+ });
+ }
+ if (!hasProposals && typeof schema.items === 'object' && !Array.isArray(schema.items) && arrayDepth < 5 /* beware of recursion */) {
+ this.addDefaultValueCompletions(schema.items, separatorAfter, collector, arrayDepth + 1);
+ }
+ };
+ JSONCompletion.prototype.addEnumValueCompletions = function (schema, separatorAfter, collector) {
+ if (objects_1.isDefined(schema.const)) {
+ collector.add({
+ kind: this.getSuggestionKind(schema.type),
+ label: this.getLabelForValue(schema.const),
+ insertText: this.getInsertTextForValue(schema.const, separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ documentation: this.fromMarkup(schema.markdownDescription) || schema.description
+ });
+ }
+ if (Array.isArray(schema.enum)) {
+ for (var i = 0, length = schema.enum.length; i < length; i++) {
+ var enm = schema.enum[i];
+ var documentation = this.fromMarkup(schema.markdownDescription) || schema.description;
+ if (schema.markdownEnumDescriptions && i < schema.markdownEnumDescriptions.length && this.doesSupportMarkdown()) {
+ documentation = this.fromMarkup(schema.markdownEnumDescriptions[i]);
+ }
+ else if (schema.enumDescriptions && i < schema.enumDescriptions.length) {
+ documentation = schema.enumDescriptions[i];
+ }
+ collector.add({
+ kind: this.getSuggestionKind(schema.type),
+ label: this.getLabelForValue(enm),
+ insertText: this.getInsertTextForValue(enm, separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ documentation: documentation
+ });
+ }
+ }
+ };
+ JSONCompletion.prototype.collectTypes = function (schema, types) {
+ if (Array.isArray(schema.enum) || objects_1.isDefined(schema.const)) {
+ return;
+ }
+ var type = schema.type;
+ if (Array.isArray(type)) {
+ type.forEach(function (t) { return types[t] = true; });
+ }
+ else if (type) {
+ types[type] = true;
+ }
+ };
+ JSONCompletion.prototype.addFillerValueCompletions = function (types, separatorAfter, collector) {
+ if (types['object']) {
+ collector.add({
+ kind: this.getSuggestionKind('object'),
+ label: '{}',
+ insertText: this.getInsertTextForGuessedValue({}, separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ detail: localize('defaults.object', 'New object'),
+ documentation: ''
+ });
+ }
+ if (types['array']) {
+ collector.add({
+ kind: this.getSuggestionKind('array'),
+ label: '[]',
+ insertText: this.getInsertTextForGuessedValue([], separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ detail: localize('defaults.array', 'New array'),
+ documentation: ''
+ });
+ }
+ };
+ JSONCompletion.prototype.addBooleanValueCompletion = function (value, separatorAfter, collector) {
+ collector.add({
+ kind: this.getSuggestionKind('boolean'),
+ label: value ? 'true' : 'false',
+ insertText: this.getInsertTextForValue(value, separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ documentation: ''
+ });
+ };
+ JSONCompletion.prototype.addNullValueCompletion = function (separatorAfter, collector) {
+ collector.add({
+ kind: this.getSuggestionKind('null'),
+ label: 'null',
+ insertText: 'null' + separatorAfter,
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
+ documentation: ''
+ });
+ };
+ JSONCompletion.prototype.addDollarSchemaCompletions = function (separatorAfter, collector) {
+ var _this = this;
+ var schemaIds = this.schemaService.getRegisteredSchemaIds(function (schema) { return schema === 'http' || schema === 'https'; });
+ schemaIds.forEach(function (schemaId) { return collector.add({
+ kind: jsonLanguageTypes_1.CompletionItemKind.Module,
+ label: _this.getLabelForValue(schemaId),
+ filterText: _this.getFilterTextForValue(schemaId),
+ insertText: _this.getInsertTextForValue(schemaId, separatorAfter),
+ insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet, documentation: ''
+ }); });
+ };
+ JSONCompletion.prototype.getLabelForValue = function (value) {
+ return JSON.stringify(value);
+ };
+ JSONCompletion.prototype.getFilterTextForValue = function (value) {
+ return JSON.stringify(value);
+ };
+ JSONCompletion.prototype.getFilterTextForSnippetValue = function (value) {
+ return JSON.stringify(value).replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
+ };
+ JSONCompletion.prototype.getLabelForSnippetValue = function (value) {
+ var label = JSON.stringify(value);
+ return label.replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
+ };
+ JSONCompletion.prototype.getInsertTextForPlainText = function (text) {
+ return text.replace(/[\\\$\}]/g, '\\$&'); // escape $, \ and }
+ };
+ JSONCompletion.prototype.getInsertTextForValue = function (value, separatorAfter) {
+ var text = JSON.stringify(value, null, '\t');
+ if (text === '{}') {
+ return '{$1}' + separatorAfter;
+ }
+ else if (text === '[]') {
+ return '[$1]' + separatorAfter;
+ }
+ return this.getInsertTextForPlainText(text + separatorAfter);
+ };
+ JSONCompletion.prototype.getInsertTextForSnippetValue = function (value, separatorAfter) {
+ var replacer = function (value) {
+ if (typeof value === 'string') {
+ if (value[0] === '^') {
+ return value.substr(1);
+ }
+ }
+ return JSON.stringify(value);
+ };
+ return json_1.stringifyObject(value, '', replacer) + separatorAfter;
+ };
+ JSONCompletion.prototype.getInsertTextForGuessedValue = function (value, separatorAfter) {
+ switch (typeof value) {
+ case 'object':
+ if (value === null) {
+ return '${1:null}' + separatorAfter;
+ }
+ return this.getInsertTextForValue(value, separatorAfter);
+ case 'string':
+ var snippetValue = JSON.stringify(value);
+ snippetValue = snippetValue.substr(1, snippetValue.length - 2); // remove quotes
+ snippetValue = this.getInsertTextForPlainText(snippetValue); // escape \ and }
+ return '"${1:' + snippetValue + '}"' + separatorAfter;
+ case 'number':
+ case 'boolean':
+ return '${1:' + JSON.stringify(value) + '}' + separatorAfter;
+ }
+ return this.getInsertTextForValue(value, separatorAfter);
+ };
+ JSONCompletion.prototype.getSuggestionKind = function (type) {
+ if (Array.isArray(type)) {
+ var array = type;
+ type = array.length > 0 ? array[0] : undefined;
+ }
+ if (!type) {
+ return jsonLanguageTypes_1.CompletionItemKind.Value;
+ }
+ switch (type) {
+ case 'string': return jsonLanguageTypes_1.CompletionItemKind.Value;
+ case 'object': return jsonLanguageTypes_1.CompletionItemKind.Module;
+ case 'property': return jsonLanguageTypes_1.CompletionItemKind.Property;
+ default: return jsonLanguageTypes_1.CompletionItemKind.Value;
+ }
+ };
+ JSONCompletion.prototype.getLabelTextForMatchingNode = function (node, document) {
+ switch (node.type) {
+ case 'array':
+ return '[]';
+ case 'object':
+ return '{}';
+ default:
+ var content = document.getText().substr(node.offset, node.length);
+ return content;
+ }
+ };
+ JSONCompletion.prototype.getInsertTextForMatchingNode = function (node, document, separatorAfter) {
+ switch (node.type) {
+ case 'array':
+ return this.getInsertTextForValue([], separatorAfter);
+ case 'object':
+ return this.getInsertTextForValue({}, separatorAfter);
+ default:
+ var content = document.getText().substr(node.offset, node.length) + separatorAfter;
+ return this.getInsertTextForPlainText(content);
+ }
+ };
+ JSONCompletion.prototype.getInsertTextForProperty = function (key, propertySchema, addValue, separatorAfter) {
+ var propertyText = this.getInsertTextForValue(key, '');
+ if (!addValue) {
+ return propertyText;
+ }
+ var resultText = propertyText + ': ';
+ var value;
+ var nValueProposals = 0;
+ if (propertySchema) {
+ if (Array.isArray(propertySchema.defaultSnippets)) {
+ if (propertySchema.defaultSnippets.length === 1) {
+ var body = propertySchema.defaultSnippets[0].body;
+ if (objects_1.isDefined(body)) {
+ value = this.getInsertTextForSnippetValue(body, '');
+ }
+ }
+ nValueProposals += propertySchema.defaultSnippets.length;
+ }
+ if (propertySchema.enum) {
+ if (!value && propertySchema.enum.length === 1) {
+ value = this.getInsertTextForGuessedValue(propertySchema.enum[0], '');
+ }
+ nValueProposals += propertySchema.enum.length;
+ }
+ if (objects_1.isDefined(propertySchema.default)) {
+ if (!value) {
+ value = this.getInsertTextForGuessedValue(propertySchema.default, '');
+ }
+ nValueProposals++;
+ }
+ if (Array.isArray(propertySchema.examples) && propertySchema.examples.length) {
+ if (!value) {
+ value = this.getInsertTextForGuessedValue(propertySchema.examples[0], '');
+ }
+ nValueProposals += propertySchema.examples.length;
+ }
+ if (nValueProposals === 0) {
+ var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
+ if (!type) {
+ if (propertySchema.properties) {
+ type = 'object';
+ }
+ else if (propertySchema.items) {
+ type = 'array';
+ }
+ }
+ switch (type) {
+ case 'boolean':
+ value = '$1';
+ break;
+ case 'string':
+ value = '"$1"';
+ break;
+ case 'object':
+ value = '{$1}';
+ break;
+ case 'array':
+ value = '[$1]';
+ break;
+ case 'number':
+ case 'integer':
+ value = '${1:0}';
+ break;
+ case 'null':
+ value = '${1:null}';
+ break;
+ default:
+ return propertyText;
+ }
+ }
+ }
+ if (!value || nValueProposals > 1) {
+ value = '$1';
+ }
+ return resultText + value + separatorAfter;
+ };
+ JSONCompletion.prototype.getCurrentWord = function (document, offset) {
+ var i = offset - 1;
+ var text = document.getText();
+ while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
+ i--;
+ }
+ return text.substring(i + 1, offset);
+ };
+ JSONCompletion.prototype.evaluateSeparatorAfter = function (document, offset) {
+ var scanner = Json.createScanner(document.getText(), true);
+ scanner.setPosition(offset);
+ var token = scanner.scan();
+ switch (token) {
+ case 5 /* CommaToken */:
+ case 2 /* CloseBraceToken */:
+ case 4 /* CloseBracketToken */:
+ case 17 /* EOF */:
+ return '';
+ default:
+ return ',';
+ }
+ };
+ JSONCompletion.prototype.findItemAtOffset = function (node, document, offset) {
+ var scanner = Json.createScanner(document.getText(), true);
+ var children = node.items;
+ for (var i = children.length - 1; i >= 0; i--) {
+ var child = children[i];
+ if (offset > child.offset + child.length) {
+ scanner.setPosition(child.offset + child.length);
+ var token = scanner.scan();
+ if (token === 5 /* CommaToken */ && offset >= scanner.getTokenOffset() + scanner.getTokenLength()) {
+ return i + 1;
+ }
+ return i;
+ }
+ else if (offset >= child.offset) {
+ return i;
+ }
+ }
+ return 0;
+ };
+ JSONCompletion.prototype.isInComment = function (document, start, offset) {
+ var scanner = Json.createScanner(document.getText(), false);
+ scanner.setPosition(start);
+ var token = scanner.scan();
+ while (token !== 17 /* EOF */ && (scanner.getTokenOffset() + scanner.getTokenLength() < offset)) {
+ token = scanner.scan();
+ }
+ return (token === 12 /* LineCommentTrivia */ || token === 13 /* BlockCommentTrivia */) && scanner.getTokenOffset() <= offset;
+ };
+ JSONCompletion.prototype.fromMarkup = function (markupString) {
+ if (markupString && this.doesSupportMarkdown()) {
+ return {
+ kind: jsonLanguageTypes_1.MarkupKind.Markdown,
+ value: markupString
+ };
+ }
+ return undefined;
+ };
+ JSONCompletion.prototype.doesSupportMarkdown = function () {
+ if (!objects_1.isDefined(this.supportsMarkdown)) {
+ var completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
+ this.supportsMarkdown = completion && completion.completionItem && Array.isArray(completion.completionItem.documentationFormat) && completion.completionItem.documentationFormat.indexOf(jsonLanguageTypes_1.MarkupKind.Markdown) !== -1;
+ }
+ return this.supportsMarkdown;
+ };
+ JSONCompletion.prototype.doesSupportsCommitCharacters = function () {
+ if (!objects_1.isDefined(this.supportsCommitCharacters)) {
+ var completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
+ this.supportsCommitCharacters = completion && completion.completionItem && !!completion.completionItem.commitCharactersSupport;
+ }
+ return this.supportsCommitCharacters;
+ };
+ return JSONCompletion;
+ }());
+ exports.JSONCompletion = JSONCompletion;
+});