1 /*---------------------------------------------------------------------------------------------
2 * Copyright (c) Microsoft Corporation. All rights reserved.
3 * Licensed under the MIT License. See License.txt in the project root for license information.
4 *--------------------------------------------------------------------------------------------*/
5 import * as Parser from '../parser/jsonParser';
6 import * as Json from 'jsonc-parser';
7 import { stringifyObject } from '../utils/json';
8 import { endsWith, extendedRegExp } from '../utils/strings';
9 import { isDefined } from '../utils/objects';
10 import { CompletionItem, CompletionItemKind, Range, TextEdit, InsertTextFormat, MarkupKind } from '../jsonLanguageTypes';
11 import * as nls from 'vscode-nls';
12 var localize = nls.loadMessageBundle();
13 var valueCommitCharacters = [',', '}', ']'];
14 var propertyCommitCharacters = [':'];
15 var JSONCompletion = /** @class */ (function () {
16 function JSONCompletion(schemaService, contributions, promiseConstructor, clientCapabilities) {
17 if (contributions === void 0) { contributions = []; }
18 if (promiseConstructor === void 0) { promiseConstructor = Promise; }
19 if (clientCapabilities === void 0) { clientCapabilities = {}; }
20 this.schemaService = schemaService;
21 this.contributions = contributions;
22 this.promiseConstructor = promiseConstructor;
23 this.clientCapabilities = clientCapabilities;
25 JSONCompletion.prototype.doResolve = function (item) {
26 for (var i = this.contributions.length - 1; i >= 0; i--) {
27 var resolveCompletion = this.contributions[i].resolveCompletion;
28 if (resolveCompletion) {
29 var resolver = resolveCompletion(item);
35 return this.promiseConstructor.resolve(item);
37 JSONCompletion.prototype.doComplete = function (document, position, doc) {
43 var text = document.getText();
44 var offset = document.offsetAt(position);
45 var node = doc.getNodeFromOffset(offset, true);
46 if (this.isInComment(document, node ? node.offset : 0, offset)) {
47 return Promise.resolve(result);
49 if (node && (offset === node.offset + node.length) && offset > 0) {
50 var ch = text[offset - 1];
51 if (node.type === 'object' && ch === '}' || node.type === 'array' && ch === ']') {
56 var currentWord = this.getCurrentWord(document, offset);
58 if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
59 overwriteRange = Range.create(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
62 var overwriteStart = offset - currentWord.length;
63 if (overwriteStart > 0 && text[overwriteStart - 1] === '"') {
66 overwriteRange = Range.create(document.positionAt(overwriteStart), position);
68 var supportsCommitCharacters = false; //this.doesSupportsCommitCharacters(); disabled for now, waiting for new API: https://github.com/microsoft/vscode/issues/42544
71 add: function (suggestion) {
72 var label = suggestion.label;
73 var existing = proposed[label];
75 label = label.replace(/[\n]/g, '↵');
76 if (label.length > 60) {
77 var shortendedLabel = label.substr(0, 57).trim() + '...';
78 if (!proposed[shortendedLabel]) {
79 label = shortendedLabel;
82 if (overwriteRange && suggestion.insertText !== undefined) {
83 suggestion.textEdit = TextEdit.replace(overwriteRange, suggestion.insertText);
85 if (supportsCommitCharacters) {
86 suggestion.commitCharacters = suggestion.kind === CompletionItemKind.Property ? propertyCommitCharacters : valueCommitCharacters;
88 suggestion.label = label;
89 proposed[label] = suggestion;
90 result.items.push(suggestion);
93 if (!existing.documentation) {
94 existing.documentation = suggestion.documentation;
96 if (!existing.detail) {
97 existing.detail = suggestion.detail;
101 setAsIncomplete: function () {
102 result.isIncomplete = true;
104 error: function (message) {
105 console.error(message);
107 log: function (message) {
108 console.log(message);
110 getNumberOfProposals: function () {
111 return result.items.length;
114 return this.schemaService.getSchemaForResource(document.uri, doc).then(function (schema) {
115 var collectionPromises = [];
118 var currentProperty = undefined;
120 if (node.type === 'string') {
121 var parent = node.parent;
122 if (parent && parent.type === 'property' && parent.keyNode === node) {
123 addValue = !parent.valueNode;
124 currentProperty = parent;
125 currentKey = text.substr(node.offset + 1, node.length - 2);
127 node = parent.parent;
132 // proposals for properties
133 if (node && node.type === 'object') {
134 // don't suggest keys when the cursor is just before the opening curly brace
135 if (node.offset === offset) {
138 // don't suggest properties that are already present
139 var properties = node.properties;
140 properties.forEach(function (p) {
141 if (!currentProperty || currentProperty !== p) {
142 proposed[p.keyNode.value] = CompletionItem.create('__');
145 var separatorAfter_1 = '';
147 separatorAfter_1 = _this.evaluateSeparatorAfter(document, document.offsetAt(overwriteRange.end));
150 // property proposals with schema
151 _this.getPropertyCompletions(schema, doc, node, addValue, separatorAfter_1, collector);
154 // property proposals without schema
155 _this.getSchemaLessPropertyCompletions(doc, node, currentKey, collector);
157 var location_1 = Parser.getNodePath(node);
158 _this.contributions.forEach(function (contribution) {
159 var collectPromise = contribution.collectPropertyCompletions(document.uri, location_1, currentWord, addValue, separatorAfter_1 === '', collector);
160 if (collectPromise) {
161 collectionPromises.push(collectPromise);
164 if ((!schema && currentWord.length > 0 && text.charAt(offset - currentWord.length - 1) !== '"')) {
166 kind: CompletionItemKind.Property,
167 label: _this.getLabelForValue(currentWord),
168 insertText: _this.getInsertTextForProperty(currentWord, undefined, false, separatorAfter_1),
169 insertTextFormat: InsertTextFormat.Snippet, documentation: '',
171 collector.setAsIncomplete();
174 // proposals for values
177 // value proposals with schema
178 _this.getValueCompletions(schema, doc, node, offset, document, collector, types);
181 // value proposals without schema
182 _this.getSchemaLessValueCompletions(doc, node, offset, document, collector);
184 if (_this.contributions.length > 0) {
185 _this.getContributedValueCompletions(doc, node, offset, document, collector, collectionPromises);
187 return _this.promiseConstructor.all(collectionPromises).then(function () {
188 if (collector.getNumberOfProposals() === 0) {
189 var offsetForSeparator = offset;
190 if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
191 offsetForSeparator = node.offset + node.length;
193 var separatorAfter = _this.evaluateSeparatorAfter(document, offsetForSeparator);
194 _this.addFillerValueCompletions(types, separatorAfter, collector);
200 JSONCompletion.prototype.getPropertyCompletions = function (schema, doc, node, addValue, separatorAfter, collector) {
202 var matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset);
203 matchingSchemas.forEach(function (s) {
204 if (s.node === node && !s.inverted) {
205 var schemaProperties_1 = s.schema.properties;
206 if (schemaProperties_1) {
207 Object.keys(schemaProperties_1).forEach(function (key) {
208 var propertySchema = schemaProperties_1[key];
209 if (typeof propertySchema === 'object' && !propertySchema.deprecationMessage && !propertySchema.doNotSuggest) {
211 kind: CompletionItemKind.Property,
213 insertText: _this.getInsertTextForProperty(key, propertySchema, addValue, separatorAfter),
214 insertTextFormat: InsertTextFormat.Snippet,
215 filterText: _this.getFilterTextForValue(key),
216 documentation: _this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
218 if (propertySchema.suggestSortText !== undefined) {
219 proposal.sortText = propertySchema.suggestSortText;
221 if (proposal.insertText && endsWith(proposal.insertText, "$1" + separatorAfter)) {
224 command: 'editor.action.triggerSuggest'
227 collector.add(proposal);
231 var schemaPropertyNames_1 = s.schema.propertyNames;
232 if (typeof schemaPropertyNames_1 === 'object' && !schemaPropertyNames_1.deprecationMessage && !schemaPropertyNames_1.doNotSuggest) {
233 var propertyNameCompletionItem = function (name, enumDescription) {
234 if (enumDescription === void 0) { enumDescription = undefined; }
236 kind: CompletionItemKind.Property,
238 insertText: _this.getInsertTextForProperty(name, undefined, addValue, separatorAfter),
239 insertTextFormat: InsertTextFormat.Snippet,
240 filterText: _this.getFilterTextForValue(name),
241 documentation: enumDescription || _this.fromMarkup(schemaPropertyNames_1.markdownDescription) || schemaPropertyNames_1.description || '',
243 if (schemaPropertyNames_1.suggestSortText !== undefined) {
244 proposal.sortText = schemaPropertyNames_1.suggestSortText;
246 if (proposal.insertText && endsWith(proposal.insertText, "$1" + separatorAfter)) {
249 command: 'editor.action.triggerSuggest'
252 collector.add(proposal);
254 if (schemaPropertyNames_1.enum) {
255 for (var i = 0; i < schemaPropertyNames_1.enum.length; i++) {
256 var enumDescription = undefined;
257 if (schemaPropertyNames_1.markdownEnumDescriptions && i < schemaPropertyNames_1.markdownEnumDescriptions.length) {
258 enumDescription = _this.fromMarkup(schemaPropertyNames_1.markdownEnumDescriptions[i]);
260 else if (schemaPropertyNames_1.enumDescriptions && i < schemaPropertyNames_1.enumDescriptions.length) {
261 enumDescription = schemaPropertyNames_1.enumDescriptions[i];
263 propertyNameCompletionItem(schemaPropertyNames_1.enum[i], enumDescription);
266 if (schemaPropertyNames_1.const) {
267 propertyNameCompletionItem(schemaPropertyNames_1.const);
273 JSONCompletion.prototype.getSchemaLessPropertyCompletions = function (doc, node, currentKey, collector) {
275 var collectCompletionsForSimilarObject = function (obj) {
276 obj.properties.forEach(function (p) {
277 var key = p.keyNode.value;
279 kind: CompletionItemKind.Property,
281 insertText: _this.getInsertTextForValue(key, ''),
282 insertTextFormat: InsertTextFormat.Snippet,
283 filterText: _this.getFilterTextForValue(key),
289 if (node.parent.type === 'property') {
290 // if the object is a property value, check the tree for other objects that hang under a property of the same name
291 var parentKey_1 = node.parent.keyNode.value;
292 doc.visit(function (n) {
293 if (n.type === 'property' && n !== node.parent && n.keyNode.value === parentKey_1 && n.valueNode && n.valueNode.type === 'object') {
294 collectCompletionsForSimilarObject(n.valueNode);
299 else if (node.parent.type === 'array') {
300 // if the object is in an array, use all other array elements as similar objects
301 node.parent.items.forEach(function (n) {
302 if (n.type === 'object' && n !== node) {
303 collectCompletionsForSimilarObject(n);
308 else if (node.type === 'object') {
310 kind: CompletionItemKind.Property,
312 insertText: this.getInsertTextForProperty('$schema', undefined, true, ''),
313 insertTextFormat: InsertTextFormat.Snippet, documentation: '',
314 filterText: this.getFilterTextForValue("$schema")
318 JSONCompletion.prototype.getSchemaLessValueCompletions = function (doc, node, offset, document, collector) {
320 var offsetForSeparator = offset;
321 if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
322 offsetForSeparator = node.offset + node.length;
327 kind: this.getSuggestionKind('object'),
328 label: 'Empty object',
329 insertText: this.getInsertTextForValue({}, ''),
330 insertTextFormat: InsertTextFormat.Snippet,
334 kind: this.getSuggestionKind('array'),
335 label: 'Empty array',
336 insertText: this.getInsertTextForValue([], ''),
337 insertTextFormat: InsertTextFormat.Snippet,
342 var separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
343 var collectSuggestionsForValues = function (value) {
344 if (value.parent && !Parser.contains(value.parent, offset, true)) {
346 kind: _this.getSuggestionKind(value.type),
347 label: _this.getLabelTextForMatchingNode(value, document),
348 insertText: _this.getInsertTextForMatchingNode(value, document, separatorAfter),
349 insertTextFormat: InsertTextFormat.Snippet, documentation: ''
352 if (value.type === 'boolean') {
353 _this.addBooleanValueCompletion(!value.value, separatorAfter, collector);
356 if (node.type === 'property') {
357 if (offset > (node.colonOffset || 0)) {
358 var valueNode = node.valueNode;
359 if (valueNode && (offset > (valueNode.offset + valueNode.length) || valueNode.type === 'object' || valueNode.type === 'array')) {
362 // suggest values at the same key
363 var parentKey_2 = node.keyNode.value;
364 doc.visit(function (n) {
365 if (n.type === 'property' && n.keyNode.value === parentKey_2 && n.valueNode) {
366 collectSuggestionsForValues(n.valueNode);
370 if (parentKey_2 === '$schema' && node.parent && !node.parent.parent) {
371 this.addDollarSchemaCompletions(separatorAfter, collector);
375 if (node.type === 'array') {
376 if (node.parent && node.parent.type === 'property') {
377 // suggest items of an array at the same key
378 var parentKey_3 = node.parent.keyNode.value;
379 doc.visit(function (n) {
380 if (n.type === 'property' && n.keyNode.value === parentKey_3 && n.valueNode && n.valueNode.type === 'array') {
381 n.valueNode.items.forEach(collectSuggestionsForValues);
387 // suggest items in the same array
388 node.items.forEach(collectSuggestionsForValues);
392 JSONCompletion.prototype.getValueCompletions = function (schema, doc, node, offset, document, collector, types) {
393 var offsetForSeparator = offset;
394 var parentKey = undefined;
395 var valueNode = undefined;
396 if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
397 offsetForSeparator = node.offset + node.length;
402 this.addSchemaValueCompletions(schema.schema, '', collector, types);
405 if ((node.type === 'property') && offset > (node.colonOffset || 0)) {
406 var valueNode_1 = node.valueNode;
407 if (valueNode_1 && offset > (valueNode_1.offset + valueNode_1.length)) {
408 return; // we are past the value node
410 parentKey = node.keyNode.value;
413 if (node && (parentKey !== undefined || node.type === 'array')) {
414 var separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
415 var matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset, valueNode);
416 for (var _i = 0, matchingSchemas_1 = matchingSchemas; _i < matchingSchemas_1.length; _i++) {
417 var s = matchingSchemas_1[_i];
418 if (s.node === node && !s.inverted && s.schema) {
419 if (node.type === 'array' && s.schema.items) {
420 if (Array.isArray(s.schema.items)) {
421 var index = this.findItemAtOffset(node, document, offset);
422 if (index < s.schema.items.length) {
423 this.addSchemaValueCompletions(s.schema.items[index], separatorAfter, collector, types);
427 this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types);
430 if (parentKey !== undefined) {
431 var propertyMatched = false;
432 if (s.schema.properties) {
433 var propertySchema = s.schema.properties[parentKey];
434 if (propertySchema) {
435 propertyMatched = true;
436 this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
439 if (s.schema.patternProperties && !propertyMatched) {
440 for (var _a = 0, _b = Object.keys(s.schema.patternProperties); _a < _b.length; _a++) {
441 var pattern = _b[_a];
442 var regex = extendedRegExp(pattern);
443 if (regex.test(parentKey)) {
444 propertyMatched = true;
445 var propertySchema = s.schema.patternProperties[pattern];
446 this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
450 if (s.schema.additionalProperties && !propertyMatched) {
451 var propertySchema = s.schema.additionalProperties;
452 this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
457 if (parentKey === '$schema' && !node.parent) {
458 this.addDollarSchemaCompletions(separatorAfter, collector);
460 if (types['boolean']) {
461 this.addBooleanValueCompletion(true, separatorAfter, collector);
462 this.addBooleanValueCompletion(false, separatorAfter, collector);
465 this.addNullValueCompletion(separatorAfter, collector);
469 JSONCompletion.prototype.getContributedValueCompletions = function (doc, node, offset, document, collector, collectionPromises) {
471 this.contributions.forEach(function (contribution) {
472 var collectPromise = contribution.collectDefaultCompletions(document.uri, collector);
473 if (collectPromise) {
474 collectionPromises.push(collectPromise);
479 if (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null') {
482 if (node && (node.type === 'property') && offset > (node.colonOffset || 0)) {
483 var parentKey_4 = node.keyNode.value;
484 var valueNode = node.valueNode;
485 if ((!valueNode || offset <= (valueNode.offset + valueNode.length)) && node.parent) {
486 var location_2 = Parser.getNodePath(node.parent);
487 this.contributions.forEach(function (contribution) {
488 var collectPromise = contribution.collectValueCompletions(document.uri, location_2, parentKey_4, collector);
489 if (collectPromise) {
490 collectionPromises.push(collectPromise);
497 JSONCompletion.prototype.addSchemaValueCompletions = function (schema, separatorAfter, collector, types) {
499 if (typeof schema === 'object') {
500 this.addEnumValueCompletions(schema, separatorAfter, collector);
501 this.addDefaultValueCompletions(schema, separatorAfter, collector);
502 this.collectTypes(schema, types);
503 if (Array.isArray(schema.allOf)) {
504 schema.allOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
506 if (Array.isArray(schema.anyOf)) {
507 schema.anyOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
509 if (Array.isArray(schema.oneOf)) {
510 schema.oneOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
514 JSONCompletion.prototype.addDefaultValueCompletions = function (schema, separatorAfter, collector, arrayDepth) {
516 if (arrayDepth === void 0) { arrayDepth = 0; }
517 var hasProposals = false;
518 if (isDefined(schema.default)) {
519 var type = schema.type;
520 var value = schema.default;
521 for (var i = arrayDepth; i > 0; i--) {
526 kind: this.getSuggestionKind(type),
527 label: this.getLabelForValue(value),
528 insertText: this.getInsertTextForValue(value, separatorAfter),
529 insertTextFormat: InsertTextFormat.Snippet,
530 detail: localize('json.suggest.default', 'Default value')
534 if (Array.isArray(schema.examples)) {
535 schema.examples.forEach(function (example) {
536 var type = schema.type;
538 for (var i = arrayDepth; i > 0; i--) {
543 kind: _this.getSuggestionKind(type),
544 label: _this.getLabelForValue(value),
545 insertText: _this.getInsertTextForValue(value, separatorAfter),
546 insertTextFormat: InsertTextFormat.Snippet
551 if (Array.isArray(schema.defaultSnippets)) {
552 schema.defaultSnippets.forEach(function (s) {
553 var type = schema.type;
558 if (isDefined(value)) {
559 var type_1 = schema.type;
560 for (var i = arrayDepth; i > 0; i--) {
564 insertText = _this.getInsertTextForSnippetValue(value, separatorAfter);
565 filterText = _this.getFilterTextForSnippetValue(value);
566 label = label || _this.getLabelForSnippetValue(value);
568 else if (typeof s.bodyText === 'string') {
569 var prefix = '', suffix = '', indent = '';
570 for (var i = arrayDepth; i > 0; i--) {
571 prefix = prefix + indent + '[\n';
572 suffix = suffix + '\n' + indent + ']';
576 insertText = prefix + indent + s.bodyText.split('\n').join('\n' + indent) + suffix + separatorAfter;
577 label = label || insertText,
578 filterText = insertText.replace(/[\n]/g, ''); // remove new lines
584 kind: _this.getSuggestionKind(type),
586 documentation: _this.fromMarkup(s.markdownDescription) || s.description,
587 insertText: insertText,
588 insertTextFormat: InsertTextFormat.Snippet,
589 filterText: filterText
594 if (!hasProposals && typeof schema.items === 'object' && !Array.isArray(schema.items) && arrayDepth < 5 /* beware of recursion */) {
595 this.addDefaultValueCompletions(schema.items, separatorAfter, collector, arrayDepth + 1);
598 JSONCompletion.prototype.addEnumValueCompletions = function (schema, separatorAfter, collector) {
599 if (isDefined(schema.const)) {
601 kind: this.getSuggestionKind(schema.type),
602 label: this.getLabelForValue(schema.const),
603 insertText: this.getInsertTextForValue(schema.const, separatorAfter),
604 insertTextFormat: InsertTextFormat.Snippet,
605 documentation: this.fromMarkup(schema.markdownDescription) || schema.description
608 if (Array.isArray(schema.enum)) {
609 for (var i = 0, length = schema.enum.length; i < length; i++) {
610 var enm = schema.enum[i];
611 var documentation = this.fromMarkup(schema.markdownDescription) || schema.description;
612 if (schema.markdownEnumDescriptions && i < schema.markdownEnumDescriptions.length && this.doesSupportMarkdown()) {
613 documentation = this.fromMarkup(schema.markdownEnumDescriptions[i]);
615 else if (schema.enumDescriptions && i < schema.enumDescriptions.length) {
616 documentation = schema.enumDescriptions[i];
619 kind: this.getSuggestionKind(schema.type),
620 label: this.getLabelForValue(enm),
621 insertText: this.getInsertTextForValue(enm, separatorAfter),
622 insertTextFormat: InsertTextFormat.Snippet,
623 documentation: documentation
628 JSONCompletion.prototype.collectTypes = function (schema, types) {
629 if (Array.isArray(schema.enum) || isDefined(schema.const)) {
632 var type = schema.type;
633 if (Array.isArray(type)) {
634 type.forEach(function (t) { return types[t] = true; });
640 JSONCompletion.prototype.addFillerValueCompletions = function (types, separatorAfter, collector) {
641 if (types['object']) {
643 kind: this.getSuggestionKind('object'),
645 insertText: this.getInsertTextForGuessedValue({}, separatorAfter),
646 insertTextFormat: InsertTextFormat.Snippet,
647 detail: localize('defaults.object', 'New object'),
651 if (types['array']) {
653 kind: this.getSuggestionKind('array'),
655 insertText: this.getInsertTextForGuessedValue([], separatorAfter),
656 insertTextFormat: InsertTextFormat.Snippet,
657 detail: localize('defaults.array', 'New array'),
662 JSONCompletion.prototype.addBooleanValueCompletion = function (value, separatorAfter, collector) {
664 kind: this.getSuggestionKind('boolean'),
665 label: value ? 'true' : 'false',
666 insertText: this.getInsertTextForValue(value, separatorAfter),
667 insertTextFormat: InsertTextFormat.Snippet,
671 JSONCompletion.prototype.addNullValueCompletion = function (separatorAfter, collector) {
673 kind: this.getSuggestionKind('null'),
675 insertText: 'null' + separatorAfter,
676 insertTextFormat: InsertTextFormat.Snippet,
680 JSONCompletion.prototype.addDollarSchemaCompletions = function (separatorAfter, collector) {
682 var schemaIds = this.schemaService.getRegisteredSchemaIds(function (schema) { return schema === 'http' || schema === 'https'; });
683 schemaIds.forEach(function (schemaId) { return collector.add({
684 kind: CompletionItemKind.Module,
685 label: _this.getLabelForValue(schemaId),
686 filterText: _this.getFilterTextForValue(schemaId),
687 insertText: _this.getInsertTextForValue(schemaId, separatorAfter),
688 insertTextFormat: InsertTextFormat.Snippet, documentation: ''
691 JSONCompletion.prototype.getLabelForValue = function (value) {
692 return JSON.stringify(value);
694 JSONCompletion.prototype.getFilterTextForValue = function (value) {
695 return JSON.stringify(value);
697 JSONCompletion.prototype.getFilterTextForSnippetValue = function (value) {
698 return JSON.stringify(value).replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
700 JSONCompletion.prototype.getLabelForSnippetValue = function (value) {
701 var label = JSON.stringify(value);
702 return label.replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
704 JSONCompletion.prototype.getInsertTextForPlainText = function (text) {
705 return text.replace(/[\\\$\}]/g, '\\$&'); // escape $, \ and }
707 JSONCompletion.prototype.getInsertTextForValue = function (value, separatorAfter) {
708 var text = JSON.stringify(value, null, '\t');
710 return '{$1}' + separatorAfter;
712 else if (text === '[]') {
713 return '[$1]' + separatorAfter;
715 return this.getInsertTextForPlainText(text + separatorAfter);
717 JSONCompletion.prototype.getInsertTextForSnippetValue = function (value, separatorAfter) {
718 var replacer = function (value) {
719 if (typeof value === 'string') {
720 if (value[0] === '^') {
721 return value.substr(1);
724 return JSON.stringify(value);
726 return stringifyObject(value, '', replacer) + separatorAfter;
728 JSONCompletion.prototype.getInsertTextForGuessedValue = function (value, separatorAfter) {
729 switch (typeof value) {
731 if (value === null) {
732 return '${1:null}' + separatorAfter;
734 return this.getInsertTextForValue(value, separatorAfter);
736 var snippetValue = JSON.stringify(value);
737 snippetValue = snippetValue.substr(1, snippetValue.length - 2); // remove quotes
738 snippetValue = this.getInsertTextForPlainText(snippetValue); // escape \ and }
739 return '"${1:' + snippetValue + '}"' + separatorAfter;
742 return '${1:' + JSON.stringify(value) + '}' + separatorAfter;
744 return this.getInsertTextForValue(value, separatorAfter);
746 JSONCompletion.prototype.getSuggestionKind = function (type) {
747 if (Array.isArray(type)) {
749 type = array.length > 0 ? array[0] : undefined;
752 return CompletionItemKind.Value;
755 case 'string': return CompletionItemKind.Value;
756 case 'object': return CompletionItemKind.Module;
757 case 'property': return CompletionItemKind.Property;
758 default: return CompletionItemKind.Value;
761 JSONCompletion.prototype.getLabelTextForMatchingNode = function (node, document) {
768 var content = document.getText().substr(node.offset, node.length);
772 JSONCompletion.prototype.getInsertTextForMatchingNode = function (node, document, separatorAfter) {
775 return this.getInsertTextForValue([], separatorAfter);
777 return this.getInsertTextForValue({}, separatorAfter);
779 var content = document.getText().substr(node.offset, node.length) + separatorAfter;
780 return this.getInsertTextForPlainText(content);
783 JSONCompletion.prototype.getInsertTextForProperty = function (key, propertySchema, addValue, separatorAfter) {
784 var propertyText = this.getInsertTextForValue(key, '');
788 var resultText = propertyText + ': ';
790 var nValueProposals = 0;
791 if (propertySchema) {
792 if (Array.isArray(propertySchema.defaultSnippets)) {
793 if (propertySchema.defaultSnippets.length === 1) {
794 var body = propertySchema.defaultSnippets[0].body;
795 if (isDefined(body)) {
796 value = this.getInsertTextForSnippetValue(body, '');
799 nValueProposals += propertySchema.defaultSnippets.length;
801 if (propertySchema.enum) {
802 if (!value && propertySchema.enum.length === 1) {
803 value = this.getInsertTextForGuessedValue(propertySchema.enum[0], '');
805 nValueProposals += propertySchema.enum.length;
807 if (isDefined(propertySchema.default)) {
809 value = this.getInsertTextForGuessedValue(propertySchema.default, '');
813 if (Array.isArray(propertySchema.examples) && propertySchema.examples.length) {
815 value = this.getInsertTextForGuessedValue(propertySchema.examples[0], '');
817 nValueProposals += propertySchema.examples.length;
819 if (nValueProposals === 0) {
820 var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
822 if (propertySchema.properties) {
825 else if (propertySchema.items) {
854 if (!value || nValueProposals > 1) {
857 return resultText + value + separatorAfter;
859 JSONCompletion.prototype.getCurrentWord = function (document, offset) {
861 var text = document.getText();
862 while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
865 return text.substring(i + 1, offset);
867 JSONCompletion.prototype.evaluateSeparatorAfter = function (document, offset) {
868 var scanner = Json.createScanner(document.getText(), true);
869 scanner.setPosition(offset);
870 var token = scanner.scan();
872 case 5 /* CommaToken */:
873 case 2 /* CloseBraceToken */:
874 case 4 /* CloseBracketToken */:
881 JSONCompletion.prototype.findItemAtOffset = function (node, document, offset) {
882 var scanner = Json.createScanner(document.getText(), true);
883 var children = node.items;
884 for (var i = children.length - 1; i >= 0; i--) {
885 var child = children[i];
886 if (offset > child.offset + child.length) {
887 scanner.setPosition(child.offset + child.length);
888 var token = scanner.scan();
889 if (token === 5 /* CommaToken */ && offset >= scanner.getTokenOffset() + scanner.getTokenLength()) {
894 else if (offset >= child.offset) {
900 JSONCompletion.prototype.isInComment = function (document, start, offset) {
901 var scanner = Json.createScanner(document.getText(), false);
902 scanner.setPosition(start);
903 var token = scanner.scan();
904 while (token !== 17 /* EOF */ && (scanner.getTokenOffset() + scanner.getTokenLength() < offset)) {
905 token = scanner.scan();
907 return (token === 12 /* LineCommentTrivia */ || token === 13 /* BlockCommentTrivia */) && scanner.getTokenOffset() <= offset;
909 JSONCompletion.prototype.fromMarkup = function (markupString) {
910 if (markupString && this.doesSupportMarkdown()) {
912 kind: MarkupKind.Markdown,
918 JSONCompletion.prototype.doesSupportMarkdown = function () {
919 if (!isDefined(this.supportsMarkdown)) {
920 var completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
921 this.supportsMarkdown = completion && completion.completionItem && Array.isArray(completion.completionItem.documentationFormat) && completion.completionItem.documentationFormat.indexOf(MarkupKind.Markdown) !== -1;
923 return this.supportsMarkdown;
925 JSONCompletion.prototype.doesSupportsCommitCharacters = function () {
926 if (!isDefined(this.supportsCommitCharacters)) {
927 var completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
928 this.supportsCommitCharacters = completion && completion.completionItem && !!completion.completionItem.commitCharactersSupport;
930 return this.supportsCommitCharacters;
932 return JSONCompletion;
934 export { JSONCompletion };