vscode-json-languageservice 5.0.0 → 5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +10 -2
  2. package/lib/esm/jsonContributions.d.ts +17 -17
  3. package/lib/esm/jsonContributions.js +1 -1
  4. package/lib/esm/jsonLanguageService.d.ts +29 -29
  5. package/lib/esm/jsonLanguageService.js +66 -66
  6. package/lib/esm/jsonLanguageTypes.d.ts +292 -279
  7. package/lib/esm/jsonLanguageTypes.js +55 -46
  8. package/lib/esm/jsonSchema.d.ts +89 -89
  9. package/lib/esm/jsonSchema.js +1 -1
  10. package/lib/esm/parser/jsonParser.js +1236 -1214
  11. package/lib/esm/services/configuration.js +528 -528
  12. package/lib/esm/services/jsonCompletion.js +924 -918
  13. package/lib/esm/services/jsonDocumentSymbols.js +267 -267
  14. package/lib/esm/services/jsonFolding.js +120 -120
  15. package/lib/esm/services/jsonHover.js +109 -109
  16. package/lib/esm/services/jsonLinks.js +72 -72
  17. package/lib/esm/services/jsonSchemaService.js +593 -586
  18. package/lib/esm/services/jsonSelectionRanges.js +61 -61
  19. package/lib/esm/services/jsonValidation.js +151 -151
  20. package/lib/esm/utils/colors.js +68 -68
  21. package/lib/esm/utils/glob.js +124 -124
  22. package/lib/esm/utils/json.js +42 -42
  23. package/lib/esm/utils/objects.js +68 -68
  24. package/lib/esm/utils/strings.js +79 -64
  25. package/lib/umd/jsonContributions.d.ts +17 -17
  26. package/lib/umd/jsonContributions.js +12 -12
  27. package/lib/umd/jsonLanguageService.d.ts +29 -29
  28. package/lib/umd/jsonLanguageService.js +94 -90
  29. package/lib/umd/jsonLanguageTypes.d.ts +292 -279
  30. package/lib/umd/jsonLanguageTypes.js +103 -93
  31. package/lib/umd/jsonSchema.d.ts +89 -89
  32. package/lib/umd/jsonSchema.js +12 -12
  33. package/lib/umd/parser/jsonParser.js +1265 -1243
  34. package/lib/umd/services/configuration.js +541 -541
  35. package/lib/umd/services/jsonCompletion.js +938 -932
  36. package/lib/umd/services/jsonDocumentSymbols.js +281 -281
  37. package/lib/umd/services/jsonFolding.js +134 -134
  38. package/lib/umd/services/jsonHover.js +123 -123
  39. package/lib/umd/services/jsonLinks.js +86 -86
  40. package/lib/umd/services/jsonSchemaService.js +609 -602
  41. package/lib/umd/services/jsonSelectionRanges.js +75 -75
  42. package/lib/umd/services/jsonValidation.js +165 -165
  43. package/lib/umd/utils/colors.js +84 -84
  44. package/lib/umd/utils/glob.js +138 -138
  45. package/lib/umd/utils/json.js +56 -56
  46. package/lib/umd/utils/objects.js +87 -87
  47. package/lib/umd/utils/strings.js +98 -82
  48. package/package.json +11 -10
@@ -1,918 +1,924 @@
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
- const localize = nls.loadMessageBundle();
13
- const valueCommitCharacters = [',', '}', ']'];
14
- const propertyCommitCharacters = [':'];
15
- export class JSONCompletion {
16
- constructor(schemaService, contributions = [], promiseConstructor = Promise, clientCapabilities = {}) {
17
- this.schemaService = schemaService;
18
- this.contributions = contributions;
19
- this.promiseConstructor = promiseConstructor;
20
- this.clientCapabilities = clientCapabilities;
21
- }
22
- doResolve(item) {
23
- for (let i = this.contributions.length - 1; i >= 0; i--) {
24
- const resolveCompletion = this.contributions[i].resolveCompletion;
25
- if (resolveCompletion) {
26
- const resolver = resolveCompletion(item);
27
- if (resolver) {
28
- return resolver;
29
- }
30
- }
31
- }
32
- return this.promiseConstructor.resolve(item);
33
- }
34
- doComplete(document, position, doc) {
35
- const result = {
36
- items: [],
37
- isIncomplete: false
38
- };
39
- const text = document.getText();
40
- const offset = document.offsetAt(position);
41
- let node = doc.getNodeFromOffset(offset, true);
42
- if (this.isInComment(document, node ? node.offset : 0, offset)) {
43
- return Promise.resolve(result);
44
- }
45
- if (node && (offset === node.offset + node.length) && offset > 0) {
46
- const ch = text[offset - 1];
47
- if (node.type === 'object' && ch === '}' || node.type === 'array' && ch === ']') {
48
- // after ] or }
49
- node = node.parent;
50
- }
51
- }
52
- const currentWord = this.getCurrentWord(document, offset);
53
- let overwriteRange;
54
- if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
55
- overwriteRange = Range.create(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
56
- }
57
- else {
58
- let overwriteStart = offset - currentWord.length;
59
- if (overwriteStart > 0 && text[overwriteStart - 1] === '"') {
60
- overwriteStart--;
61
- }
62
- overwriteRange = Range.create(document.positionAt(overwriteStart), position);
63
- }
64
- const supportsCommitCharacters = false; //this.doesSupportsCommitCharacters(); disabled for now, waiting for new API: https://github.com/microsoft/vscode/issues/42544
65
- const proposed = {};
66
- const collector = {
67
- add: (suggestion) => {
68
- let label = suggestion.label;
69
- const existing = proposed[label];
70
- if (!existing) {
71
- label = label.replace(/[\n]/g, '↵');
72
- if (label.length > 60) {
73
- const shortendedLabel = label.substr(0, 57).trim() + '...';
74
- if (!proposed[shortendedLabel]) {
75
- label = shortendedLabel;
76
- }
77
- }
78
- if (overwriteRange && suggestion.insertText !== undefined) {
79
- suggestion.textEdit = TextEdit.replace(overwriteRange, suggestion.insertText);
80
- }
81
- if (supportsCommitCharacters) {
82
- suggestion.commitCharacters = suggestion.kind === CompletionItemKind.Property ? propertyCommitCharacters : valueCommitCharacters;
83
- }
84
- suggestion.label = label;
85
- proposed[label] = suggestion;
86
- result.items.push(suggestion);
87
- }
88
- else {
89
- if (!existing.documentation) {
90
- existing.documentation = suggestion.documentation;
91
- }
92
- if (!existing.detail) {
93
- existing.detail = suggestion.detail;
94
- }
95
- }
96
- },
97
- setAsIncomplete: () => {
98
- result.isIncomplete = true;
99
- },
100
- error: (message) => {
101
- console.error(message);
102
- },
103
- log: (message) => {
104
- console.log(message);
105
- },
106
- getNumberOfProposals: () => {
107
- return result.items.length;
108
- }
109
- };
110
- return this.schemaService.getSchemaForResource(document.uri, doc).then((schema) => {
111
- const collectionPromises = [];
112
- let addValue = true;
113
- let currentKey = '';
114
- let currentProperty = undefined;
115
- if (node) {
116
- if (node.type === 'string') {
117
- const parent = node.parent;
118
- if (parent && parent.type === 'property' && parent.keyNode === node) {
119
- addValue = !parent.valueNode;
120
- currentProperty = parent;
121
- currentKey = text.substr(node.offset + 1, node.length - 2);
122
- if (parent) {
123
- node = parent.parent;
124
- }
125
- }
126
- }
127
- }
128
- // proposals for properties
129
- if (node && node.type === 'object') {
130
- // don't suggest keys when the cursor is just before the opening curly brace
131
- if (node.offset === offset) {
132
- return result;
133
- }
134
- // don't suggest properties that are already present
135
- const properties = node.properties;
136
- properties.forEach(p => {
137
- if (!currentProperty || currentProperty !== p) {
138
- proposed[p.keyNode.value] = CompletionItem.create('__');
139
- }
140
- });
141
- let separatorAfter = '';
142
- if (addValue) {
143
- separatorAfter = this.evaluateSeparatorAfter(document, document.offsetAt(overwriteRange.end));
144
- }
145
- if (schema) {
146
- // property proposals with schema
147
- this.getPropertyCompletions(schema, doc, node, addValue, separatorAfter, collector);
148
- }
149
- else {
150
- // property proposals without schema
151
- this.getSchemaLessPropertyCompletions(doc, node, currentKey, collector);
152
- }
153
- const location = Parser.getNodePath(node);
154
- this.contributions.forEach((contribution) => {
155
- const collectPromise = contribution.collectPropertyCompletions(document.uri, location, currentWord, addValue, separatorAfter === '', collector);
156
- if (collectPromise) {
157
- collectionPromises.push(collectPromise);
158
- }
159
- });
160
- if ((!schema && currentWord.length > 0 && text.charAt(offset - currentWord.length - 1) !== '"')) {
161
- collector.add({
162
- kind: CompletionItemKind.Property,
163
- label: this.getLabelForValue(currentWord),
164
- insertText: this.getInsertTextForProperty(currentWord, undefined, false, separatorAfter),
165
- insertTextFormat: InsertTextFormat.Snippet, documentation: '',
166
- });
167
- collector.setAsIncomplete();
168
- }
169
- }
170
- // proposals for values
171
- const types = {};
172
- if (schema) {
173
- // value proposals with schema
174
- this.getValueCompletions(schema, doc, node, offset, document, collector, types);
175
- }
176
- else {
177
- // value proposals without schema
178
- this.getSchemaLessValueCompletions(doc, node, offset, document, collector);
179
- }
180
- if (this.contributions.length > 0) {
181
- this.getContributedValueCompletions(doc, node, offset, document, collector, collectionPromises);
182
- }
183
- return this.promiseConstructor.all(collectionPromises).then(() => {
184
- if (collector.getNumberOfProposals() === 0) {
185
- let offsetForSeparator = offset;
186
- if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
187
- offsetForSeparator = node.offset + node.length;
188
- }
189
- const separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
190
- this.addFillerValueCompletions(types, separatorAfter, collector);
191
- }
192
- return result;
193
- });
194
- });
195
- }
196
- getPropertyCompletions(schema, doc, node, addValue, separatorAfter, collector) {
197
- const matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset);
198
- matchingSchemas.forEach((s) => {
199
- if (s.node === node && !s.inverted) {
200
- const schemaProperties = s.schema.properties;
201
- if (schemaProperties) {
202
- Object.keys(schemaProperties).forEach((key) => {
203
- const propertySchema = schemaProperties[key];
204
- if (typeof propertySchema === 'object' && !propertySchema.deprecationMessage && !propertySchema.doNotSuggest) {
205
- const proposal = {
206
- kind: CompletionItemKind.Property,
207
- label: key,
208
- insertText: this.getInsertTextForProperty(key, propertySchema, addValue, separatorAfter),
209
- insertTextFormat: InsertTextFormat.Snippet,
210
- filterText: this.getFilterTextForValue(key),
211
- documentation: this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
212
- };
213
- if (propertySchema.suggestSortText !== undefined) {
214
- proposal.sortText = propertySchema.suggestSortText;
215
- }
216
- if (proposal.insertText && endsWith(proposal.insertText, `$1${separatorAfter}`)) {
217
- proposal.command = {
218
- title: 'Suggest',
219
- command: 'editor.action.triggerSuggest'
220
- };
221
- }
222
- collector.add(proposal);
223
- }
224
- });
225
- }
226
- const schemaPropertyNames = s.schema.propertyNames;
227
- if (typeof schemaPropertyNames === 'object' && !schemaPropertyNames.deprecationMessage && !schemaPropertyNames.doNotSuggest) {
228
- const propertyNameCompletionItem = (name, enumDescription = undefined) => {
229
- const proposal = {
230
- kind: CompletionItemKind.Property,
231
- label: name,
232
- insertText: this.getInsertTextForProperty(name, undefined, addValue, separatorAfter),
233
- insertTextFormat: InsertTextFormat.Snippet,
234
- filterText: this.getFilterTextForValue(name),
235
- documentation: enumDescription || this.fromMarkup(schemaPropertyNames.markdownDescription) || schemaPropertyNames.description || '',
236
- };
237
- if (schemaPropertyNames.suggestSortText !== undefined) {
238
- proposal.sortText = schemaPropertyNames.suggestSortText;
239
- }
240
- if (proposal.insertText && endsWith(proposal.insertText, `$1${separatorAfter}`)) {
241
- proposal.command = {
242
- title: 'Suggest',
243
- command: 'editor.action.triggerSuggest'
244
- };
245
- }
246
- collector.add(proposal);
247
- };
248
- if (schemaPropertyNames.enum) {
249
- for (let i = 0; i < schemaPropertyNames.enum.length; i++) {
250
- let enumDescription = undefined;
251
- if (schemaPropertyNames.markdownEnumDescriptions && i < schemaPropertyNames.markdownEnumDescriptions.length) {
252
- enumDescription = this.fromMarkup(schemaPropertyNames.markdownEnumDescriptions[i]);
253
- }
254
- else if (schemaPropertyNames.enumDescriptions && i < schemaPropertyNames.enumDescriptions.length) {
255
- enumDescription = schemaPropertyNames.enumDescriptions[i];
256
- }
257
- propertyNameCompletionItem(schemaPropertyNames.enum[i], enumDescription);
258
- }
259
- }
260
- if (schemaPropertyNames.const) {
261
- propertyNameCompletionItem(schemaPropertyNames.const);
262
- }
263
- }
264
- }
265
- });
266
- }
267
- getSchemaLessPropertyCompletions(doc, node, currentKey, collector) {
268
- const collectCompletionsForSimilarObject = (obj) => {
269
- obj.properties.forEach((p) => {
270
- const key = p.keyNode.value;
271
- collector.add({
272
- kind: CompletionItemKind.Property,
273
- label: key,
274
- insertText: this.getInsertTextForValue(key, ''),
275
- insertTextFormat: InsertTextFormat.Snippet,
276
- filterText: this.getFilterTextForValue(key),
277
- documentation: ''
278
- });
279
- });
280
- };
281
- if (node.parent) {
282
- if (node.parent.type === 'property') {
283
- // if the object is a property value, check the tree for other objects that hang under a property of the same name
284
- const parentKey = node.parent.keyNode.value;
285
- doc.visit(n => {
286
- if (n.type === 'property' && n !== node.parent && n.keyNode.value === parentKey && n.valueNode && n.valueNode.type === 'object') {
287
- collectCompletionsForSimilarObject(n.valueNode);
288
- }
289
- return true;
290
- });
291
- }
292
- else if (node.parent.type === 'array') {
293
- // if the object is in an array, use all other array elements as similar objects
294
- node.parent.items.forEach(n => {
295
- if (n.type === 'object' && n !== node) {
296
- collectCompletionsForSimilarObject(n);
297
- }
298
- });
299
- }
300
- }
301
- else if (node.type === 'object') {
302
- collector.add({
303
- kind: CompletionItemKind.Property,
304
- label: '$schema',
305
- insertText: this.getInsertTextForProperty('$schema', undefined, true, ''),
306
- insertTextFormat: InsertTextFormat.Snippet, documentation: '',
307
- filterText: this.getFilterTextForValue("$schema")
308
- });
309
- }
310
- }
311
- getSchemaLessValueCompletions(doc, node, offset, document, collector) {
312
- let offsetForSeparator = offset;
313
- if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
314
- offsetForSeparator = node.offset + node.length;
315
- node = node.parent;
316
- }
317
- if (!node) {
318
- collector.add({
319
- kind: this.getSuggestionKind('object'),
320
- label: 'Empty object',
321
- insertText: this.getInsertTextForValue({}, ''),
322
- insertTextFormat: InsertTextFormat.Snippet,
323
- documentation: ''
324
- });
325
- collector.add({
326
- kind: this.getSuggestionKind('array'),
327
- label: 'Empty array',
328
- insertText: this.getInsertTextForValue([], ''),
329
- insertTextFormat: InsertTextFormat.Snippet,
330
- documentation: ''
331
- });
332
- return;
333
- }
334
- const separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
335
- const collectSuggestionsForValues = (value) => {
336
- if (value.parent && !Parser.contains(value.parent, offset, true)) {
337
- collector.add({
338
- kind: this.getSuggestionKind(value.type),
339
- label: this.getLabelTextForMatchingNode(value, document),
340
- insertText: this.getInsertTextForMatchingNode(value, document, separatorAfter),
341
- insertTextFormat: InsertTextFormat.Snippet, documentation: ''
342
- });
343
- }
344
- if (value.type === 'boolean') {
345
- this.addBooleanValueCompletion(!value.value, separatorAfter, collector);
346
- }
347
- };
348
- if (node.type === 'property') {
349
- if (offset > (node.colonOffset || 0)) {
350
- const valueNode = node.valueNode;
351
- if (valueNode && (offset > (valueNode.offset + valueNode.length) || valueNode.type === 'object' || valueNode.type === 'array')) {
352
- return;
353
- }
354
- // suggest values at the same key
355
- const parentKey = node.keyNode.value;
356
- doc.visit(n => {
357
- if (n.type === 'property' && n.keyNode.value === parentKey && n.valueNode) {
358
- collectSuggestionsForValues(n.valueNode);
359
- }
360
- return true;
361
- });
362
- if (parentKey === '$schema' && node.parent && !node.parent.parent) {
363
- this.addDollarSchemaCompletions(separatorAfter, collector);
364
- }
365
- }
366
- }
367
- if (node.type === 'array') {
368
- if (node.parent && node.parent.type === 'property') {
369
- // suggest items of an array at the same key
370
- const parentKey = node.parent.keyNode.value;
371
- doc.visit((n) => {
372
- if (n.type === 'property' && n.keyNode.value === parentKey && n.valueNode && n.valueNode.type === 'array') {
373
- n.valueNode.items.forEach(collectSuggestionsForValues);
374
- }
375
- return true;
376
- });
377
- }
378
- else {
379
- // suggest items in the same array
380
- node.items.forEach(collectSuggestionsForValues);
381
- }
382
- }
383
- }
384
- getValueCompletions(schema, doc, node, offset, document, collector, types) {
385
- let offsetForSeparator = offset;
386
- let parentKey = undefined;
387
- let valueNode = undefined;
388
- if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
389
- offsetForSeparator = node.offset + node.length;
390
- valueNode = node;
391
- node = node.parent;
392
- }
393
- if (!node) {
394
- this.addSchemaValueCompletions(schema.schema, '', collector, types);
395
- return;
396
- }
397
- if ((node.type === 'property') && offset > (node.colonOffset || 0)) {
398
- const valueNode = node.valueNode;
399
- if (valueNode && offset > (valueNode.offset + valueNode.length)) {
400
- return; // we are past the value node
401
- }
402
- parentKey = node.keyNode.value;
403
- node = node.parent;
404
- }
405
- if (node && (parentKey !== undefined || node.type === 'array')) {
406
- const separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
407
- const matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset, valueNode);
408
- for (const s of matchingSchemas) {
409
- if (s.node === node && !s.inverted && s.schema) {
410
- if (node.type === 'array' && s.schema.items) {
411
- if (Array.isArray(s.schema.items)) {
412
- const index = this.findItemAtOffset(node, document, offset);
413
- if (index < s.schema.items.length) {
414
- this.addSchemaValueCompletions(s.schema.items[index], separatorAfter, collector, types);
415
- }
416
- }
417
- else {
418
- this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types);
419
- }
420
- }
421
- if (parentKey !== undefined) {
422
- let propertyMatched = false;
423
- if (s.schema.properties) {
424
- const propertySchema = s.schema.properties[parentKey];
425
- if (propertySchema) {
426
- propertyMatched = true;
427
- this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
428
- }
429
- }
430
- if (s.schema.patternProperties && !propertyMatched) {
431
- for (const pattern of Object.keys(s.schema.patternProperties)) {
432
- const regex = extendedRegExp(pattern);
433
- if (regex?.test(parentKey)) {
434
- propertyMatched = true;
435
- const propertySchema = s.schema.patternProperties[pattern];
436
- this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
437
- }
438
- }
439
- }
440
- if (s.schema.additionalProperties && !propertyMatched) {
441
- const propertySchema = s.schema.additionalProperties;
442
- this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
443
- }
444
- }
445
- }
446
- }
447
- if (parentKey === '$schema' && !node.parent) {
448
- this.addDollarSchemaCompletions(separatorAfter, collector);
449
- }
450
- if (types['boolean']) {
451
- this.addBooleanValueCompletion(true, separatorAfter, collector);
452
- this.addBooleanValueCompletion(false, separatorAfter, collector);
453
- }
454
- if (types['null']) {
455
- this.addNullValueCompletion(separatorAfter, collector);
456
- }
457
- }
458
- }
459
- getContributedValueCompletions(doc, node, offset, document, collector, collectionPromises) {
460
- if (!node) {
461
- this.contributions.forEach((contribution) => {
462
- const collectPromise = contribution.collectDefaultCompletions(document.uri, collector);
463
- if (collectPromise) {
464
- collectionPromises.push(collectPromise);
465
- }
466
- });
467
- }
468
- else {
469
- if (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null') {
470
- node = node.parent;
471
- }
472
- if (node && (node.type === 'property') && offset > (node.colonOffset || 0)) {
473
- const parentKey = node.keyNode.value;
474
- const valueNode = node.valueNode;
475
- if ((!valueNode || offset <= (valueNode.offset + valueNode.length)) && node.parent) {
476
- const location = Parser.getNodePath(node.parent);
477
- this.contributions.forEach((contribution) => {
478
- const collectPromise = contribution.collectValueCompletions(document.uri, location, parentKey, collector);
479
- if (collectPromise) {
480
- collectionPromises.push(collectPromise);
481
- }
482
- });
483
- }
484
- }
485
- }
486
- }
487
- addSchemaValueCompletions(schema, separatorAfter, collector, types) {
488
- if (typeof schema === 'object') {
489
- this.addEnumValueCompletions(schema, separatorAfter, collector);
490
- this.addDefaultValueCompletions(schema, separatorAfter, collector);
491
- this.collectTypes(schema, types);
492
- if (Array.isArray(schema.allOf)) {
493
- schema.allOf.forEach(s => this.addSchemaValueCompletions(s, separatorAfter, collector, types));
494
- }
495
- if (Array.isArray(schema.anyOf)) {
496
- schema.anyOf.forEach(s => this.addSchemaValueCompletions(s, separatorAfter, collector, types));
497
- }
498
- if (Array.isArray(schema.oneOf)) {
499
- schema.oneOf.forEach(s => this.addSchemaValueCompletions(s, separatorAfter, collector, types));
500
- }
501
- }
502
- }
503
- addDefaultValueCompletions(schema, separatorAfter, collector, arrayDepth = 0) {
504
- let hasProposals = false;
505
- if (isDefined(schema.default)) {
506
- let type = schema.type;
507
- let value = schema.default;
508
- for (let i = arrayDepth; i > 0; i--) {
509
- value = [value];
510
- type = 'array';
511
- }
512
- collector.add({
513
- kind: this.getSuggestionKind(type),
514
- label: this.getLabelForValue(value),
515
- insertText: this.getInsertTextForValue(value, separatorAfter),
516
- insertTextFormat: InsertTextFormat.Snippet,
517
- detail: localize('json.suggest.default', 'Default value')
518
- });
519
- hasProposals = true;
520
- }
521
- if (Array.isArray(schema.examples)) {
522
- schema.examples.forEach(example => {
523
- let type = schema.type;
524
- let value = example;
525
- for (let i = arrayDepth; i > 0; i--) {
526
- value = [value];
527
- type = 'array';
528
- }
529
- collector.add({
530
- kind: this.getSuggestionKind(type),
531
- label: this.getLabelForValue(value),
532
- insertText: this.getInsertTextForValue(value, separatorAfter),
533
- insertTextFormat: InsertTextFormat.Snippet
534
- });
535
- hasProposals = true;
536
- });
537
- }
538
- if (Array.isArray(schema.defaultSnippets)) {
539
- schema.defaultSnippets.forEach(s => {
540
- let type = schema.type;
541
- let value = s.body;
542
- let label = s.label;
543
- let insertText;
544
- let filterText;
545
- if (isDefined(value)) {
546
- let type = schema.type;
547
- for (let i = arrayDepth; i > 0; i--) {
548
- value = [value];
549
- type = 'array';
550
- }
551
- insertText = this.getInsertTextForSnippetValue(value, separatorAfter);
552
- filterText = this.getFilterTextForSnippetValue(value);
553
- label = label || this.getLabelForSnippetValue(value);
554
- }
555
- else if (typeof s.bodyText === 'string') {
556
- let prefix = '', suffix = '', indent = '';
557
- for (let i = arrayDepth; i > 0; i--) {
558
- prefix = prefix + indent + '[\n';
559
- suffix = suffix + '\n' + indent + ']';
560
- indent += '\t';
561
- type = 'array';
562
- }
563
- insertText = prefix + indent + s.bodyText.split('\n').join('\n' + indent) + suffix + separatorAfter;
564
- label = label || insertText,
565
- filterText = insertText.replace(/[\n]/g, ''); // remove new lines
566
- }
567
- else {
568
- return;
569
- }
570
- collector.add({
571
- kind: this.getSuggestionKind(type),
572
- label,
573
- documentation: this.fromMarkup(s.markdownDescription) || s.description,
574
- insertText,
575
- insertTextFormat: InsertTextFormat.Snippet,
576
- filterText
577
- });
578
- hasProposals = true;
579
- });
580
- }
581
- if (!hasProposals && typeof schema.items === 'object' && !Array.isArray(schema.items) && arrayDepth < 5 /* beware of recursion */) {
582
- this.addDefaultValueCompletions(schema.items, separatorAfter, collector, arrayDepth + 1);
583
- }
584
- }
585
- addEnumValueCompletions(schema, separatorAfter, collector) {
586
- if (isDefined(schema.const)) {
587
- collector.add({
588
- kind: this.getSuggestionKind(schema.type),
589
- label: this.getLabelForValue(schema.const),
590
- insertText: this.getInsertTextForValue(schema.const, separatorAfter),
591
- insertTextFormat: InsertTextFormat.Snippet,
592
- documentation: this.fromMarkup(schema.markdownDescription) || schema.description
593
- });
594
- }
595
- if (Array.isArray(schema.enum)) {
596
- for (let i = 0, length = schema.enum.length; i < length; i++) {
597
- const enm = schema.enum[i];
598
- let documentation = this.fromMarkup(schema.markdownDescription) || schema.description;
599
- if (schema.markdownEnumDescriptions && i < schema.markdownEnumDescriptions.length && this.doesSupportMarkdown()) {
600
- documentation = this.fromMarkup(schema.markdownEnumDescriptions[i]);
601
- }
602
- else if (schema.enumDescriptions && i < schema.enumDescriptions.length) {
603
- documentation = schema.enumDescriptions[i];
604
- }
605
- collector.add({
606
- kind: this.getSuggestionKind(schema.type),
607
- label: this.getLabelForValue(enm),
608
- insertText: this.getInsertTextForValue(enm, separatorAfter),
609
- insertTextFormat: InsertTextFormat.Snippet,
610
- documentation
611
- });
612
- }
613
- }
614
- }
615
- collectTypes(schema, types) {
616
- if (Array.isArray(schema.enum) || isDefined(schema.const)) {
617
- return;
618
- }
619
- const type = schema.type;
620
- if (Array.isArray(type)) {
621
- type.forEach(t => types[t] = true);
622
- }
623
- else if (type) {
624
- types[type] = true;
625
- }
626
- }
627
- addFillerValueCompletions(types, separatorAfter, collector) {
628
- if (types['object']) {
629
- collector.add({
630
- kind: this.getSuggestionKind('object'),
631
- label: '{}',
632
- insertText: this.getInsertTextForGuessedValue({}, separatorAfter),
633
- insertTextFormat: InsertTextFormat.Snippet,
634
- detail: localize('defaults.object', 'New object'),
635
- documentation: ''
636
- });
637
- }
638
- if (types['array']) {
639
- collector.add({
640
- kind: this.getSuggestionKind('array'),
641
- label: '[]',
642
- insertText: this.getInsertTextForGuessedValue([], separatorAfter),
643
- insertTextFormat: InsertTextFormat.Snippet,
644
- detail: localize('defaults.array', 'New array'),
645
- documentation: ''
646
- });
647
- }
648
- }
649
- addBooleanValueCompletion(value, separatorAfter, collector) {
650
- collector.add({
651
- kind: this.getSuggestionKind('boolean'),
652
- label: value ? 'true' : 'false',
653
- insertText: this.getInsertTextForValue(value, separatorAfter),
654
- insertTextFormat: InsertTextFormat.Snippet,
655
- documentation: ''
656
- });
657
- }
658
- addNullValueCompletion(separatorAfter, collector) {
659
- collector.add({
660
- kind: this.getSuggestionKind('null'),
661
- label: 'null',
662
- insertText: 'null' + separatorAfter,
663
- insertTextFormat: InsertTextFormat.Snippet,
664
- documentation: ''
665
- });
666
- }
667
- addDollarSchemaCompletions(separatorAfter, collector) {
668
- const schemaIds = this.schemaService.getRegisteredSchemaIds(schema => schema === 'http' || schema === 'https');
669
- schemaIds.forEach(schemaId => collector.add({
670
- kind: CompletionItemKind.Module,
671
- label: this.getLabelForValue(schemaId),
672
- filterText: this.getFilterTextForValue(schemaId),
673
- insertText: this.getInsertTextForValue(schemaId, separatorAfter),
674
- insertTextFormat: InsertTextFormat.Snippet, documentation: ''
675
- }));
676
- }
677
- getLabelForValue(value) {
678
- return JSON.stringify(value);
679
- }
680
- getFilterTextForValue(value) {
681
- return JSON.stringify(value);
682
- }
683
- getFilterTextForSnippetValue(value) {
684
- return JSON.stringify(value).replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
685
- }
686
- getLabelForSnippetValue(value) {
687
- const label = JSON.stringify(value);
688
- return label.replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
689
- }
690
- getInsertTextForPlainText(text) {
691
- return text.replace(/[\\\$\}]/g, '\\$&'); // escape $, \ and }
692
- }
693
- getInsertTextForValue(value, separatorAfter) {
694
- var text = JSON.stringify(value, null, '\t');
695
- if (text === '{}') {
696
- return '{$1}' + separatorAfter;
697
- }
698
- else if (text === '[]') {
699
- return '[$1]' + separatorAfter;
700
- }
701
- return this.getInsertTextForPlainText(text + separatorAfter);
702
- }
703
- getInsertTextForSnippetValue(value, separatorAfter) {
704
- const replacer = (value) => {
705
- if (typeof value === 'string') {
706
- if (value[0] === '^') {
707
- return value.substr(1);
708
- }
709
- }
710
- return JSON.stringify(value);
711
- };
712
- return stringifyObject(value, '', replacer) + separatorAfter;
713
- }
714
- getInsertTextForGuessedValue(value, separatorAfter) {
715
- switch (typeof value) {
716
- case 'object':
717
- if (value === null) {
718
- return '${1:null}' + separatorAfter;
719
- }
720
- return this.getInsertTextForValue(value, separatorAfter);
721
- case 'string':
722
- let snippetValue = JSON.stringify(value);
723
- snippetValue = snippetValue.substr(1, snippetValue.length - 2); // remove quotes
724
- snippetValue = this.getInsertTextForPlainText(snippetValue); // escape \ and }
725
- return '"${1:' + snippetValue + '}"' + separatorAfter;
726
- case 'number':
727
- case 'boolean':
728
- return '${1:' + JSON.stringify(value) + '}' + separatorAfter;
729
- }
730
- return this.getInsertTextForValue(value, separatorAfter);
731
- }
732
- getSuggestionKind(type) {
733
- if (Array.isArray(type)) {
734
- const array = type;
735
- type = array.length > 0 ? array[0] : undefined;
736
- }
737
- if (!type) {
738
- return CompletionItemKind.Value;
739
- }
740
- switch (type) {
741
- case 'string': return CompletionItemKind.Value;
742
- case 'object': return CompletionItemKind.Module;
743
- case 'property': return CompletionItemKind.Property;
744
- default: return CompletionItemKind.Value;
745
- }
746
- }
747
- getLabelTextForMatchingNode(node, document) {
748
- switch (node.type) {
749
- case 'array':
750
- return '[]';
751
- case 'object':
752
- return '{}';
753
- default:
754
- const content = document.getText().substr(node.offset, node.length);
755
- return content;
756
- }
757
- }
758
- getInsertTextForMatchingNode(node, document, separatorAfter) {
759
- switch (node.type) {
760
- case 'array':
761
- return this.getInsertTextForValue([], separatorAfter);
762
- case 'object':
763
- return this.getInsertTextForValue({}, separatorAfter);
764
- default:
765
- const content = document.getText().substr(node.offset, node.length) + separatorAfter;
766
- return this.getInsertTextForPlainText(content);
767
- }
768
- }
769
- getInsertTextForProperty(key, propertySchema, addValue, separatorAfter) {
770
- const propertyText = this.getInsertTextForValue(key, '');
771
- if (!addValue) {
772
- return propertyText;
773
- }
774
- const resultText = propertyText + ': ';
775
- let value;
776
- let nValueProposals = 0;
777
- if (propertySchema) {
778
- if (Array.isArray(propertySchema.defaultSnippets)) {
779
- if (propertySchema.defaultSnippets.length === 1) {
780
- const body = propertySchema.defaultSnippets[0].body;
781
- if (isDefined(body)) {
782
- value = this.getInsertTextForSnippetValue(body, '');
783
- }
784
- }
785
- nValueProposals += propertySchema.defaultSnippets.length;
786
- }
787
- if (propertySchema.enum) {
788
- if (!value && propertySchema.enum.length === 1) {
789
- value = this.getInsertTextForGuessedValue(propertySchema.enum[0], '');
790
- }
791
- nValueProposals += propertySchema.enum.length;
792
- }
793
- if (isDefined(propertySchema.default)) {
794
- if (!value) {
795
- value = this.getInsertTextForGuessedValue(propertySchema.default, '');
796
- }
797
- nValueProposals++;
798
- }
799
- if (Array.isArray(propertySchema.examples) && propertySchema.examples.length) {
800
- if (!value) {
801
- value = this.getInsertTextForGuessedValue(propertySchema.examples[0], '');
802
- }
803
- nValueProposals += propertySchema.examples.length;
804
- }
805
- if (nValueProposals === 0) {
806
- var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
807
- if (!type) {
808
- if (propertySchema.properties) {
809
- type = 'object';
810
- }
811
- else if (propertySchema.items) {
812
- type = 'array';
813
- }
814
- }
815
- switch (type) {
816
- case 'boolean':
817
- value = '$1';
818
- break;
819
- case 'string':
820
- value = '"$1"';
821
- break;
822
- case 'object':
823
- value = '{$1}';
824
- break;
825
- case 'array':
826
- value = '[$1]';
827
- break;
828
- case 'number':
829
- case 'integer':
830
- value = '${1:0}';
831
- break;
832
- case 'null':
833
- value = '${1:null}';
834
- break;
835
- default:
836
- return propertyText;
837
- }
838
- }
839
- }
840
- if (!value || nValueProposals > 1) {
841
- value = '$1';
842
- }
843
- return resultText + value + separatorAfter;
844
- }
845
- getCurrentWord(document, offset) {
846
- var i = offset - 1;
847
- var text = document.getText();
848
- while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
849
- i--;
850
- }
851
- return text.substring(i + 1, offset);
852
- }
853
- evaluateSeparatorAfter(document, offset) {
854
- const scanner = Json.createScanner(document.getText(), true);
855
- scanner.setPosition(offset);
856
- const token = scanner.scan();
857
- switch (token) {
858
- case 5 /* CommaToken */:
859
- case 2 /* CloseBraceToken */:
860
- case 4 /* CloseBracketToken */:
861
- case 17 /* EOF */:
862
- return '';
863
- default:
864
- return ',';
865
- }
866
- }
867
- findItemAtOffset(node, document, offset) {
868
- const scanner = Json.createScanner(document.getText(), true);
869
- const children = node.items;
870
- for (let i = children.length - 1; i >= 0; i--) {
871
- const child = children[i];
872
- if (offset > child.offset + child.length) {
873
- scanner.setPosition(child.offset + child.length);
874
- const token = scanner.scan();
875
- if (token === 5 /* CommaToken */ && offset >= scanner.getTokenOffset() + scanner.getTokenLength()) {
876
- return i + 1;
877
- }
878
- return i;
879
- }
880
- else if (offset >= child.offset) {
881
- return i;
882
- }
883
- }
884
- return 0;
885
- }
886
- isInComment(document, start, offset) {
887
- const scanner = Json.createScanner(document.getText(), false);
888
- scanner.setPosition(start);
889
- let token = scanner.scan();
890
- while (token !== 17 /* EOF */ && (scanner.getTokenOffset() + scanner.getTokenLength() < offset)) {
891
- token = scanner.scan();
892
- }
893
- return (token === 12 /* LineCommentTrivia */ || token === 13 /* BlockCommentTrivia */) && scanner.getTokenOffset() <= offset;
894
- }
895
- fromMarkup(markupString) {
896
- if (markupString && this.doesSupportMarkdown()) {
897
- return {
898
- kind: MarkupKind.Markdown,
899
- value: markupString
900
- };
901
- }
902
- return undefined;
903
- }
904
- doesSupportMarkdown() {
905
- if (!isDefined(this.supportsMarkdown)) {
906
- const completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
907
- this.supportsMarkdown = completion && completion.completionItem && Array.isArray(completion.completionItem.documentationFormat) && completion.completionItem.documentationFormat.indexOf(MarkupKind.Markdown) !== -1;
908
- }
909
- return this.supportsMarkdown;
910
- }
911
- doesSupportsCommitCharacters() {
912
- if (!isDefined(this.supportsCommitCharacters)) {
913
- const completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
914
- this.supportsCommitCharacters = completion && completion.completionItem && !!completion.completionItem.commitCharactersSupport;
915
- }
916
- return this.supportsCommitCharacters;
917
- }
918
- }
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
+ const localize = nls.loadMessageBundle();
13
+ const valueCommitCharacters = [',', '}', ']'];
14
+ const propertyCommitCharacters = [':'];
15
+ export class JSONCompletion {
16
+ constructor(schemaService, contributions = [], promiseConstructor = Promise, clientCapabilities = {}) {
17
+ this.schemaService = schemaService;
18
+ this.contributions = contributions;
19
+ this.promiseConstructor = promiseConstructor;
20
+ this.clientCapabilities = clientCapabilities;
21
+ }
22
+ doResolve(item) {
23
+ for (let i = this.contributions.length - 1; i >= 0; i--) {
24
+ const resolveCompletion = this.contributions[i].resolveCompletion;
25
+ if (resolveCompletion) {
26
+ const resolver = resolveCompletion(item);
27
+ if (resolver) {
28
+ return resolver;
29
+ }
30
+ }
31
+ }
32
+ return this.promiseConstructor.resolve(item);
33
+ }
34
+ doComplete(document, position, doc) {
35
+ const result = {
36
+ items: [],
37
+ isIncomplete: false
38
+ };
39
+ const text = document.getText();
40
+ const offset = document.offsetAt(position);
41
+ let node = doc.getNodeFromOffset(offset, true);
42
+ if (this.isInComment(document, node ? node.offset : 0, offset)) {
43
+ return Promise.resolve(result);
44
+ }
45
+ if (node && (offset === node.offset + node.length) && offset > 0) {
46
+ const ch = text[offset - 1];
47
+ if (node.type === 'object' && ch === '}' || node.type === 'array' && ch === ']') {
48
+ // after ] or }
49
+ node = node.parent;
50
+ }
51
+ }
52
+ const currentWord = this.getCurrentWord(document, offset);
53
+ let overwriteRange;
54
+ if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
55
+ overwriteRange = Range.create(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
56
+ }
57
+ else {
58
+ let overwriteStart = offset - currentWord.length;
59
+ if (overwriteStart > 0 && text[overwriteStart - 1] === '"') {
60
+ overwriteStart--;
61
+ }
62
+ overwriteRange = Range.create(document.positionAt(overwriteStart), position);
63
+ }
64
+ const supportsCommitCharacters = false; //this.doesSupportsCommitCharacters(); disabled for now, waiting for new API: https://github.com/microsoft/vscode/issues/42544
65
+ const proposed = {};
66
+ const collector = {
67
+ add: (suggestion) => {
68
+ let label = suggestion.label;
69
+ const existing = proposed[label];
70
+ if (!existing) {
71
+ label = label.replace(/[\n]/g, '↵');
72
+ if (label.length > 60) {
73
+ const shortendedLabel = label.substr(0, 57).trim() + '...';
74
+ if (!proposed[shortendedLabel]) {
75
+ label = shortendedLabel;
76
+ }
77
+ }
78
+ if (overwriteRange && suggestion.insertText !== undefined) {
79
+ suggestion.textEdit = TextEdit.replace(overwriteRange, suggestion.insertText);
80
+ }
81
+ if (supportsCommitCharacters) {
82
+ suggestion.commitCharacters = suggestion.kind === CompletionItemKind.Property ? propertyCommitCharacters : valueCommitCharacters;
83
+ }
84
+ suggestion.label = label;
85
+ proposed[label] = suggestion;
86
+ result.items.push(suggestion);
87
+ }
88
+ else {
89
+ if (!existing.documentation) {
90
+ existing.documentation = suggestion.documentation;
91
+ }
92
+ if (!existing.detail) {
93
+ existing.detail = suggestion.detail;
94
+ }
95
+ }
96
+ },
97
+ setAsIncomplete: () => {
98
+ result.isIncomplete = true;
99
+ },
100
+ error: (message) => {
101
+ console.error(message);
102
+ },
103
+ log: (message) => {
104
+ console.log(message);
105
+ },
106
+ getNumberOfProposals: () => {
107
+ return result.items.length;
108
+ }
109
+ };
110
+ return this.schemaService.getSchemaForResource(document.uri, doc).then((schema) => {
111
+ const collectionPromises = [];
112
+ let addValue = true;
113
+ let currentKey = '';
114
+ let currentProperty = undefined;
115
+ if (node) {
116
+ if (node.type === 'string') {
117
+ const parent = node.parent;
118
+ if (parent && parent.type === 'property' && parent.keyNode === node) {
119
+ addValue = !parent.valueNode;
120
+ currentProperty = parent;
121
+ currentKey = text.substr(node.offset + 1, node.length - 2);
122
+ if (parent) {
123
+ node = parent.parent;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ // proposals for properties
129
+ if (node && node.type === 'object') {
130
+ // don't suggest keys when the cursor is just before the opening curly brace
131
+ if (node.offset === offset) {
132
+ return result;
133
+ }
134
+ // don't suggest properties that are already present
135
+ const properties = node.properties;
136
+ properties.forEach(p => {
137
+ if (!currentProperty || currentProperty !== p) {
138
+ proposed[p.keyNode.value] = CompletionItem.create('__');
139
+ }
140
+ });
141
+ let separatorAfter = '';
142
+ if (addValue) {
143
+ separatorAfter = this.evaluateSeparatorAfter(document, document.offsetAt(overwriteRange.end));
144
+ }
145
+ if (schema) {
146
+ // property proposals with schema
147
+ this.getPropertyCompletions(schema, doc, node, addValue, separatorAfter, collector);
148
+ }
149
+ else {
150
+ // property proposals without schema
151
+ this.getSchemaLessPropertyCompletions(doc, node, currentKey, collector);
152
+ }
153
+ const location = Parser.getNodePath(node);
154
+ this.contributions.forEach((contribution) => {
155
+ const collectPromise = contribution.collectPropertyCompletions(document.uri, location, currentWord, addValue, separatorAfter === '', collector);
156
+ if (collectPromise) {
157
+ collectionPromises.push(collectPromise);
158
+ }
159
+ });
160
+ if ((!schema && currentWord.length > 0 && text.charAt(offset - currentWord.length - 1) !== '"')) {
161
+ collector.add({
162
+ kind: CompletionItemKind.Property,
163
+ label: this.getLabelForValue(currentWord),
164
+ insertText: this.getInsertTextForProperty(currentWord, undefined, false, separatorAfter),
165
+ insertTextFormat: InsertTextFormat.Snippet, documentation: '',
166
+ });
167
+ collector.setAsIncomplete();
168
+ }
169
+ }
170
+ // proposals for values
171
+ const types = {};
172
+ if (schema) {
173
+ // value proposals with schema
174
+ this.getValueCompletions(schema, doc, node, offset, document, collector, types);
175
+ }
176
+ else {
177
+ // value proposals without schema
178
+ this.getSchemaLessValueCompletions(doc, node, offset, document, collector);
179
+ }
180
+ if (this.contributions.length > 0) {
181
+ this.getContributedValueCompletions(doc, node, offset, document, collector, collectionPromises);
182
+ }
183
+ return this.promiseConstructor.all(collectionPromises).then(() => {
184
+ if (collector.getNumberOfProposals() === 0) {
185
+ let offsetForSeparator = offset;
186
+ if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
187
+ offsetForSeparator = node.offset + node.length;
188
+ }
189
+ const separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
190
+ this.addFillerValueCompletions(types, separatorAfter, collector);
191
+ }
192
+ return result;
193
+ });
194
+ });
195
+ }
196
+ getPropertyCompletions(schema, doc, node, addValue, separatorAfter, collector) {
197
+ const matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset);
198
+ matchingSchemas.forEach((s) => {
199
+ if (s.node === node && !s.inverted) {
200
+ const schemaProperties = s.schema.properties;
201
+ if (schemaProperties) {
202
+ Object.keys(schemaProperties).forEach((key) => {
203
+ const propertySchema = schemaProperties[key];
204
+ if (typeof propertySchema === 'object' && !propertySchema.deprecationMessage && !propertySchema.doNotSuggest) {
205
+ const proposal = {
206
+ kind: CompletionItemKind.Property,
207
+ label: key,
208
+ insertText: this.getInsertTextForProperty(key, propertySchema, addValue, separatorAfter),
209
+ insertTextFormat: InsertTextFormat.Snippet,
210
+ filterText: this.getFilterTextForValue(key),
211
+ documentation: this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
212
+ };
213
+ if (propertySchema.suggestSortText !== undefined) {
214
+ proposal.sortText = propertySchema.suggestSortText;
215
+ }
216
+ if (proposal.insertText && endsWith(proposal.insertText, `$1${separatorAfter}`)) {
217
+ proposal.command = {
218
+ title: 'Suggest',
219
+ command: 'editor.action.triggerSuggest'
220
+ };
221
+ }
222
+ collector.add(proposal);
223
+ }
224
+ });
225
+ }
226
+ const schemaPropertyNames = s.schema.propertyNames;
227
+ if (typeof schemaPropertyNames === 'object' && !schemaPropertyNames.deprecationMessage && !schemaPropertyNames.doNotSuggest) {
228
+ const propertyNameCompletionItem = (name, enumDescription = undefined) => {
229
+ const proposal = {
230
+ kind: CompletionItemKind.Property,
231
+ label: name,
232
+ insertText: this.getInsertTextForProperty(name, undefined, addValue, separatorAfter),
233
+ insertTextFormat: InsertTextFormat.Snippet,
234
+ filterText: this.getFilterTextForValue(name),
235
+ documentation: enumDescription || this.fromMarkup(schemaPropertyNames.markdownDescription) || schemaPropertyNames.description || '',
236
+ };
237
+ if (schemaPropertyNames.suggestSortText !== undefined) {
238
+ proposal.sortText = schemaPropertyNames.suggestSortText;
239
+ }
240
+ if (proposal.insertText && endsWith(proposal.insertText, `$1${separatorAfter}`)) {
241
+ proposal.command = {
242
+ title: 'Suggest',
243
+ command: 'editor.action.triggerSuggest'
244
+ };
245
+ }
246
+ collector.add(proposal);
247
+ };
248
+ if (schemaPropertyNames.enum) {
249
+ for (let i = 0; i < schemaPropertyNames.enum.length; i++) {
250
+ let enumDescription = undefined;
251
+ if (schemaPropertyNames.markdownEnumDescriptions && i < schemaPropertyNames.markdownEnumDescriptions.length) {
252
+ enumDescription = this.fromMarkup(schemaPropertyNames.markdownEnumDescriptions[i]);
253
+ }
254
+ else if (schemaPropertyNames.enumDescriptions && i < schemaPropertyNames.enumDescriptions.length) {
255
+ enumDescription = schemaPropertyNames.enumDescriptions[i];
256
+ }
257
+ propertyNameCompletionItem(schemaPropertyNames.enum[i], enumDescription);
258
+ }
259
+ }
260
+ if (schemaPropertyNames.const) {
261
+ propertyNameCompletionItem(schemaPropertyNames.const);
262
+ }
263
+ }
264
+ }
265
+ });
266
+ }
267
+ getSchemaLessPropertyCompletions(doc, node, currentKey, collector) {
268
+ const collectCompletionsForSimilarObject = (obj) => {
269
+ obj.properties.forEach((p) => {
270
+ const key = p.keyNode.value;
271
+ collector.add({
272
+ kind: CompletionItemKind.Property,
273
+ label: key,
274
+ insertText: this.getInsertTextForValue(key, ''),
275
+ insertTextFormat: InsertTextFormat.Snippet,
276
+ filterText: this.getFilterTextForValue(key),
277
+ documentation: ''
278
+ });
279
+ });
280
+ };
281
+ if (node.parent) {
282
+ if (node.parent.type === 'property') {
283
+ // if the object is a property value, check the tree for other objects that hang under a property of the same name
284
+ const parentKey = node.parent.keyNode.value;
285
+ doc.visit(n => {
286
+ if (n.type === 'property' && n !== node.parent && n.keyNode.value === parentKey && n.valueNode && n.valueNode.type === 'object') {
287
+ collectCompletionsForSimilarObject(n.valueNode);
288
+ }
289
+ return true;
290
+ });
291
+ }
292
+ else if (node.parent.type === 'array') {
293
+ // if the object is in an array, use all other array elements as similar objects
294
+ node.parent.items.forEach(n => {
295
+ if (n.type === 'object' && n !== node) {
296
+ collectCompletionsForSimilarObject(n);
297
+ }
298
+ });
299
+ }
300
+ }
301
+ else if (node.type === 'object') {
302
+ collector.add({
303
+ kind: CompletionItemKind.Property,
304
+ label: '$schema',
305
+ insertText: this.getInsertTextForProperty('$schema', undefined, true, ''),
306
+ insertTextFormat: InsertTextFormat.Snippet, documentation: '',
307
+ filterText: this.getFilterTextForValue("$schema")
308
+ });
309
+ }
310
+ }
311
+ getSchemaLessValueCompletions(doc, node, offset, document, collector) {
312
+ let offsetForSeparator = offset;
313
+ if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
314
+ offsetForSeparator = node.offset + node.length;
315
+ node = node.parent;
316
+ }
317
+ if (!node) {
318
+ collector.add({
319
+ kind: this.getSuggestionKind('object'),
320
+ label: 'Empty object',
321
+ insertText: this.getInsertTextForValue({}, ''),
322
+ insertTextFormat: InsertTextFormat.Snippet,
323
+ documentation: ''
324
+ });
325
+ collector.add({
326
+ kind: this.getSuggestionKind('array'),
327
+ label: 'Empty array',
328
+ insertText: this.getInsertTextForValue([], ''),
329
+ insertTextFormat: InsertTextFormat.Snippet,
330
+ documentation: ''
331
+ });
332
+ return;
333
+ }
334
+ const separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
335
+ const collectSuggestionsForValues = (value) => {
336
+ if (value.parent && !Parser.contains(value.parent, offset, true)) {
337
+ collector.add({
338
+ kind: this.getSuggestionKind(value.type),
339
+ label: this.getLabelTextForMatchingNode(value, document),
340
+ insertText: this.getInsertTextForMatchingNode(value, document, separatorAfter),
341
+ insertTextFormat: InsertTextFormat.Snippet, documentation: ''
342
+ });
343
+ }
344
+ if (value.type === 'boolean') {
345
+ this.addBooleanValueCompletion(!value.value, separatorAfter, collector);
346
+ }
347
+ };
348
+ if (node.type === 'property') {
349
+ if (offset > (node.colonOffset || 0)) {
350
+ const valueNode = node.valueNode;
351
+ if (valueNode && (offset > (valueNode.offset + valueNode.length) || valueNode.type === 'object' || valueNode.type === 'array')) {
352
+ return;
353
+ }
354
+ // suggest values at the same key
355
+ const parentKey = node.keyNode.value;
356
+ doc.visit(n => {
357
+ if (n.type === 'property' && n.keyNode.value === parentKey && n.valueNode) {
358
+ collectSuggestionsForValues(n.valueNode);
359
+ }
360
+ return true;
361
+ });
362
+ if (parentKey === '$schema' && node.parent && !node.parent.parent) {
363
+ this.addDollarSchemaCompletions(separatorAfter, collector);
364
+ }
365
+ }
366
+ }
367
+ if (node.type === 'array') {
368
+ if (node.parent && node.parent.type === 'property') {
369
+ // suggest items of an array at the same key
370
+ const parentKey = node.parent.keyNode.value;
371
+ doc.visit((n) => {
372
+ if (n.type === 'property' && n.keyNode.value === parentKey && n.valueNode && n.valueNode.type === 'array') {
373
+ n.valueNode.items.forEach(collectSuggestionsForValues);
374
+ }
375
+ return true;
376
+ });
377
+ }
378
+ else {
379
+ // suggest items in the same array
380
+ node.items.forEach(collectSuggestionsForValues);
381
+ }
382
+ }
383
+ }
384
+ getValueCompletions(schema, doc, node, offset, document, collector, types) {
385
+ let offsetForSeparator = offset;
386
+ let parentKey = undefined;
387
+ let valueNode = undefined;
388
+ if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
389
+ offsetForSeparator = node.offset + node.length;
390
+ valueNode = node;
391
+ node = node.parent;
392
+ }
393
+ if (!node) {
394
+ this.addSchemaValueCompletions(schema.schema, '', collector, types);
395
+ return;
396
+ }
397
+ if ((node.type === 'property') && offset > (node.colonOffset || 0)) {
398
+ const valueNode = node.valueNode;
399
+ if (valueNode && offset > (valueNode.offset + valueNode.length)) {
400
+ return; // we are past the value node
401
+ }
402
+ parentKey = node.keyNode.value;
403
+ node = node.parent;
404
+ }
405
+ if (node && (parentKey !== undefined || node.type === 'array')) {
406
+ const separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
407
+ const matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset, valueNode);
408
+ for (const s of matchingSchemas) {
409
+ if (s.node === node && !s.inverted && s.schema) {
410
+ if (node.type === 'array' && s.schema.items) {
411
+ if (Array.isArray(s.schema.items)) {
412
+ const index = this.findItemAtOffset(node, document, offset);
413
+ if (index < s.schema.items.length) {
414
+ this.addSchemaValueCompletions(s.schema.items[index], separatorAfter, collector, types);
415
+ }
416
+ }
417
+ else {
418
+ this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types);
419
+ }
420
+ }
421
+ if (parentKey !== undefined) {
422
+ let propertyMatched = false;
423
+ if (s.schema.properties) {
424
+ const propertySchema = s.schema.properties[parentKey];
425
+ if (propertySchema) {
426
+ propertyMatched = true;
427
+ this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
428
+ }
429
+ }
430
+ if (s.schema.patternProperties && !propertyMatched) {
431
+ for (const pattern of Object.keys(s.schema.patternProperties)) {
432
+ const regex = extendedRegExp(pattern);
433
+ if (regex?.test(parentKey)) {
434
+ propertyMatched = true;
435
+ const propertySchema = s.schema.patternProperties[pattern];
436
+ this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
437
+ }
438
+ }
439
+ }
440
+ if (s.schema.additionalProperties && !propertyMatched) {
441
+ const propertySchema = s.schema.additionalProperties;
442
+ this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
443
+ }
444
+ }
445
+ }
446
+ }
447
+ if (parentKey === '$schema' && !node.parent) {
448
+ this.addDollarSchemaCompletions(separatorAfter, collector);
449
+ }
450
+ if (types['boolean']) {
451
+ this.addBooleanValueCompletion(true, separatorAfter, collector);
452
+ this.addBooleanValueCompletion(false, separatorAfter, collector);
453
+ }
454
+ if (types['null']) {
455
+ this.addNullValueCompletion(separatorAfter, collector);
456
+ }
457
+ }
458
+ }
459
+ getContributedValueCompletions(doc, node, offset, document, collector, collectionPromises) {
460
+ if (!node) {
461
+ this.contributions.forEach((contribution) => {
462
+ const collectPromise = contribution.collectDefaultCompletions(document.uri, collector);
463
+ if (collectPromise) {
464
+ collectionPromises.push(collectPromise);
465
+ }
466
+ });
467
+ }
468
+ else {
469
+ if (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null') {
470
+ node = node.parent;
471
+ }
472
+ if (node && (node.type === 'property') && offset > (node.colonOffset || 0)) {
473
+ const parentKey = node.keyNode.value;
474
+ const valueNode = node.valueNode;
475
+ if ((!valueNode || offset <= (valueNode.offset + valueNode.length)) && node.parent) {
476
+ const location = Parser.getNodePath(node.parent);
477
+ this.contributions.forEach((contribution) => {
478
+ const collectPromise = contribution.collectValueCompletions(document.uri, location, parentKey, collector);
479
+ if (collectPromise) {
480
+ collectionPromises.push(collectPromise);
481
+ }
482
+ });
483
+ }
484
+ }
485
+ }
486
+ }
487
+ addSchemaValueCompletions(schema, separatorAfter, collector, types) {
488
+ if (typeof schema === 'object') {
489
+ this.addEnumValueCompletions(schema, separatorAfter, collector);
490
+ this.addDefaultValueCompletions(schema, separatorAfter, collector);
491
+ this.collectTypes(schema, types);
492
+ if (Array.isArray(schema.allOf)) {
493
+ schema.allOf.forEach(s => this.addSchemaValueCompletions(s, separatorAfter, collector, types));
494
+ }
495
+ if (Array.isArray(schema.anyOf)) {
496
+ schema.anyOf.forEach(s => this.addSchemaValueCompletions(s, separatorAfter, collector, types));
497
+ }
498
+ if (Array.isArray(schema.oneOf)) {
499
+ schema.oneOf.forEach(s => this.addSchemaValueCompletions(s, separatorAfter, collector, types));
500
+ }
501
+ }
502
+ }
503
+ addDefaultValueCompletions(schema, separatorAfter, collector, arrayDepth = 0) {
504
+ let hasProposals = false;
505
+ if (isDefined(schema.default)) {
506
+ let type = schema.type;
507
+ let value = schema.default;
508
+ for (let i = arrayDepth; i > 0; i--) {
509
+ value = [value];
510
+ type = 'array';
511
+ }
512
+ collector.add({
513
+ kind: this.getSuggestionKind(type),
514
+ label: this.getLabelForValue(value),
515
+ insertText: this.getInsertTextForValue(value, separatorAfter),
516
+ insertTextFormat: InsertTextFormat.Snippet,
517
+ detail: localize('json.suggest.default', 'Default value')
518
+ });
519
+ hasProposals = true;
520
+ }
521
+ if (Array.isArray(schema.examples)) {
522
+ schema.examples.forEach(example => {
523
+ let type = schema.type;
524
+ let value = example;
525
+ for (let i = arrayDepth; i > 0; i--) {
526
+ value = [value];
527
+ type = 'array';
528
+ }
529
+ collector.add({
530
+ kind: this.getSuggestionKind(type),
531
+ label: this.getLabelForValue(value),
532
+ insertText: this.getInsertTextForValue(value, separatorAfter),
533
+ insertTextFormat: InsertTextFormat.Snippet
534
+ });
535
+ hasProposals = true;
536
+ });
537
+ }
538
+ if (Array.isArray(schema.defaultSnippets)) {
539
+ schema.defaultSnippets.forEach(s => {
540
+ let type = schema.type;
541
+ let value = s.body;
542
+ let label = s.label;
543
+ let insertText;
544
+ let filterText;
545
+ if (isDefined(value)) {
546
+ let type = schema.type;
547
+ for (let i = arrayDepth; i > 0; i--) {
548
+ value = [value];
549
+ type = 'array';
550
+ }
551
+ insertText = this.getInsertTextForSnippetValue(value, separatorAfter);
552
+ filterText = this.getFilterTextForSnippetValue(value);
553
+ label = label || this.getLabelForSnippetValue(value);
554
+ }
555
+ else if (typeof s.bodyText === 'string') {
556
+ let prefix = '', suffix = '', indent = '';
557
+ for (let i = arrayDepth; i > 0; i--) {
558
+ prefix = prefix + indent + '[\n';
559
+ suffix = suffix + '\n' + indent + ']';
560
+ indent += '\t';
561
+ type = 'array';
562
+ }
563
+ insertText = prefix + indent + s.bodyText.split('\n').join('\n' + indent) + suffix + separatorAfter;
564
+ label = label || insertText,
565
+ filterText = insertText.replace(/[\n]/g, ''); // remove new lines
566
+ }
567
+ else {
568
+ return;
569
+ }
570
+ collector.add({
571
+ kind: this.getSuggestionKind(type),
572
+ label,
573
+ documentation: this.fromMarkup(s.markdownDescription) || s.description,
574
+ insertText,
575
+ insertTextFormat: InsertTextFormat.Snippet,
576
+ filterText
577
+ });
578
+ hasProposals = true;
579
+ });
580
+ }
581
+ if (!hasProposals && typeof schema.items === 'object' && !Array.isArray(schema.items) && arrayDepth < 5 /* beware of recursion */) {
582
+ this.addDefaultValueCompletions(schema.items, separatorAfter, collector, arrayDepth + 1);
583
+ }
584
+ }
585
+ addEnumValueCompletions(schema, separatorAfter, collector) {
586
+ if (isDefined(schema.const)) {
587
+ collector.add({
588
+ kind: this.getSuggestionKind(schema.type),
589
+ label: this.getLabelForValue(schema.const),
590
+ insertText: this.getInsertTextForValue(schema.const, separatorAfter),
591
+ insertTextFormat: InsertTextFormat.Snippet,
592
+ documentation: this.fromMarkup(schema.markdownDescription) || schema.description
593
+ });
594
+ }
595
+ if (Array.isArray(schema.enum)) {
596
+ for (let i = 0, length = schema.enum.length; i < length; i++) {
597
+ const enm = schema.enum[i];
598
+ let documentation = this.fromMarkup(schema.markdownDescription) || schema.description;
599
+ if (schema.markdownEnumDescriptions && i < schema.markdownEnumDescriptions.length && this.doesSupportMarkdown()) {
600
+ documentation = this.fromMarkup(schema.markdownEnumDescriptions[i]);
601
+ }
602
+ else if (schema.enumDescriptions && i < schema.enumDescriptions.length) {
603
+ documentation = schema.enumDescriptions[i];
604
+ }
605
+ collector.add({
606
+ kind: this.getSuggestionKind(schema.type),
607
+ label: this.getLabelForValue(enm),
608
+ insertText: this.getInsertTextForValue(enm, separatorAfter),
609
+ insertTextFormat: InsertTextFormat.Snippet,
610
+ documentation
611
+ });
612
+ }
613
+ }
614
+ }
615
+ collectTypes(schema, types) {
616
+ if (Array.isArray(schema.enum) || isDefined(schema.const)) {
617
+ return;
618
+ }
619
+ const type = schema.type;
620
+ if (Array.isArray(type)) {
621
+ type.forEach(t => types[t] = true);
622
+ }
623
+ else if (type) {
624
+ types[type] = true;
625
+ }
626
+ }
627
+ addFillerValueCompletions(types, separatorAfter, collector) {
628
+ if (types['object']) {
629
+ collector.add({
630
+ kind: this.getSuggestionKind('object'),
631
+ label: '{}',
632
+ insertText: this.getInsertTextForGuessedValue({}, separatorAfter),
633
+ insertTextFormat: InsertTextFormat.Snippet,
634
+ detail: localize('defaults.object', 'New object'),
635
+ documentation: ''
636
+ });
637
+ }
638
+ if (types['array']) {
639
+ collector.add({
640
+ kind: this.getSuggestionKind('array'),
641
+ label: '[]',
642
+ insertText: this.getInsertTextForGuessedValue([], separatorAfter),
643
+ insertTextFormat: InsertTextFormat.Snippet,
644
+ detail: localize('defaults.array', 'New array'),
645
+ documentation: ''
646
+ });
647
+ }
648
+ }
649
+ addBooleanValueCompletion(value, separatorAfter, collector) {
650
+ collector.add({
651
+ kind: this.getSuggestionKind('boolean'),
652
+ label: value ? 'true' : 'false',
653
+ insertText: this.getInsertTextForValue(value, separatorAfter),
654
+ insertTextFormat: InsertTextFormat.Snippet,
655
+ documentation: ''
656
+ });
657
+ }
658
+ addNullValueCompletion(separatorAfter, collector) {
659
+ collector.add({
660
+ kind: this.getSuggestionKind('null'),
661
+ label: 'null',
662
+ insertText: 'null' + separatorAfter,
663
+ insertTextFormat: InsertTextFormat.Snippet,
664
+ documentation: ''
665
+ });
666
+ }
667
+ addDollarSchemaCompletions(separatorAfter, collector) {
668
+ const schemaIds = this.schemaService.getRegisteredSchemaIds(schema => schema === 'http' || schema === 'https');
669
+ schemaIds.forEach(schemaId => collector.add({
670
+ kind: CompletionItemKind.Module,
671
+ label: this.getLabelForValue(schemaId),
672
+ filterText: this.getFilterTextForValue(schemaId),
673
+ insertText: this.getInsertTextForValue(schemaId, separatorAfter),
674
+ insertTextFormat: InsertTextFormat.Snippet, documentation: ''
675
+ }));
676
+ }
677
+ getLabelForValue(value) {
678
+ return JSON.stringify(value);
679
+ }
680
+ getFilterTextForValue(value) {
681
+ return JSON.stringify(value);
682
+ }
683
+ getFilterTextForSnippetValue(value) {
684
+ return JSON.stringify(value).replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
685
+ }
686
+ getLabelForSnippetValue(value) {
687
+ const label = JSON.stringify(value);
688
+ return label.replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
689
+ }
690
+ getInsertTextForPlainText(text) {
691
+ return text.replace(/[\\\$\}]/g, '\\$&'); // escape $, \ and }
692
+ }
693
+ getInsertTextForValue(value, separatorAfter) {
694
+ var text = JSON.stringify(value, null, '\t');
695
+ if (text === '{}') {
696
+ return '{$1}' + separatorAfter;
697
+ }
698
+ else if (text === '[]') {
699
+ return '[$1]' + separatorAfter;
700
+ }
701
+ return this.getInsertTextForPlainText(text + separatorAfter);
702
+ }
703
+ getInsertTextForSnippetValue(value, separatorAfter) {
704
+ const replacer = (value) => {
705
+ if (typeof value === 'string') {
706
+ if (value[0] === '^') {
707
+ return value.substr(1);
708
+ }
709
+ }
710
+ return JSON.stringify(value);
711
+ };
712
+ return stringifyObject(value, '', replacer) + separatorAfter;
713
+ }
714
+ getInsertTextForGuessedValue(value, separatorAfter) {
715
+ switch (typeof value) {
716
+ case 'object':
717
+ if (value === null) {
718
+ return '${1:null}' + separatorAfter;
719
+ }
720
+ return this.getInsertTextForValue(value, separatorAfter);
721
+ case 'string':
722
+ let snippetValue = JSON.stringify(value);
723
+ snippetValue = snippetValue.substr(1, snippetValue.length - 2); // remove quotes
724
+ snippetValue = this.getInsertTextForPlainText(snippetValue); // escape \ and }
725
+ return '"${1:' + snippetValue + '}"' + separatorAfter;
726
+ case 'number':
727
+ case 'boolean':
728
+ return '${1:' + JSON.stringify(value) + '}' + separatorAfter;
729
+ }
730
+ return this.getInsertTextForValue(value, separatorAfter);
731
+ }
732
+ getSuggestionKind(type) {
733
+ if (Array.isArray(type)) {
734
+ const array = type;
735
+ type = array.length > 0 ? array[0] : undefined;
736
+ }
737
+ if (!type) {
738
+ return CompletionItemKind.Value;
739
+ }
740
+ switch (type) {
741
+ case 'string': return CompletionItemKind.Value;
742
+ case 'object': return CompletionItemKind.Module;
743
+ case 'property': return CompletionItemKind.Property;
744
+ default: return CompletionItemKind.Value;
745
+ }
746
+ }
747
+ getLabelTextForMatchingNode(node, document) {
748
+ switch (node.type) {
749
+ case 'array':
750
+ return '[]';
751
+ case 'object':
752
+ return '{}';
753
+ default:
754
+ const content = document.getText().substr(node.offset, node.length);
755
+ return content;
756
+ }
757
+ }
758
+ getInsertTextForMatchingNode(node, document, separatorAfter) {
759
+ switch (node.type) {
760
+ case 'array':
761
+ return this.getInsertTextForValue([], separatorAfter);
762
+ case 'object':
763
+ return this.getInsertTextForValue({}, separatorAfter);
764
+ default:
765
+ const content = document.getText().substr(node.offset, node.length) + separatorAfter;
766
+ return this.getInsertTextForPlainText(content);
767
+ }
768
+ }
769
+ getInsertTextForProperty(key, propertySchema, addValue, separatorAfter) {
770
+ const propertyText = this.getInsertTextForValue(key, '');
771
+ if (!addValue) {
772
+ return propertyText;
773
+ }
774
+ const resultText = propertyText + ': ';
775
+ let value;
776
+ let nValueProposals = 0;
777
+ if (propertySchema) {
778
+ if (Array.isArray(propertySchema.defaultSnippets)) {
779
+ if (propertySchema.defaultSnippets.length === 1) {
780
+ const body = propertySchema.defaultSnippets[0].body;
781
+ if (isDefined(body)) {
782
+ value = this.getInsertTextForSnippetValue(body, '');
783
+ }
784
+ }
785
+ nValueProposals += propertySchema.defaultSnippets.length;
786
+ }
787
+ if (propertySchema.enum) {
788
+ if (!value && propertySchema.enum.length === 1) {
789
+ value = this.getInsertTextForGuessedValue(propertySchema.enum[0], '');
790
+ }
791
+ nValueProposals += propertySchema.enum.length;
792
+ }
793
+ if (isDefined(propertySchema.const)) {
794
+ if (!value) {
795
+ value = this.getInsertTextForGuessedValue(propertySchema.const, '');
796
+ }
797
+ nValueProposals++;
798
+ }
799
+ if (isDefined(propertySchema.default)) {
800
+ if (!value) {
801
+ value = this.getInsertTextForGuessedValue(propertySchema.default, '');
802
+ }
803
+ nValueProposals++;
804
+ }
805
+ if (Array.isArray(propertySchema.examples) && propertySchema.examples.length) {
806
+ if (!value) {
807
+ value = this.getInsertTextForGuessedValue(propertySchema.examples[0], '');
808
+ }
809
+ nValueProposals += propertySchema.examples.length;
810
+ }
811
+ if (nValueProposals === 0) {
812
+ var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
813
+ if (!type) {
814
+ if (propertySchema.properties) {
815
+ type = 'object';
816
+ }
817
+ else if (propertySchema.items) {
818
+ type = 'array';
819
+ }
820
+ }
821
+ switch (type) {
822
+ case 'boolean':
823
+ value = '$1';
824
+ break;
825
+ case 'string':
826
+ value = '"$1"';
827
+ break;
828
+ case 'object':
829
+ value = '{$1}';
830
+ break;
831
+ case 'array':
832
+ value = '[$1]';
833
+ break;
834
+ case 'number':
835
+ case 'integer':
836
+ value = '${1:0}';
837
+ break;
838
+ case 'null':
839
+ value = '${1:null}';
840
+ break;
841
+ default:
842
+ return propertyText;
843
+ }
844
+ }
845
+ }
846
+ if (!value || nValueProposals > 1) {
847
+ value = '$1';
848
+ }
849
+ return resultText + value + separatorAfter;
850
+ }
851
+ getCurrentWord(document, offset) {
852
+ var i = offset - 1;
853
+ var text = document.getText();
854
+ while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
855
+ i--;
856
+ }
857
+ return text.substring(i + 1, offset);
858
+ }
859
+ evaluateSeparatorAfter(document, offset) {
860
+ const scanner = Json.createScanner(document.getText(), true);
861
+ scanner.setPosition(offset);
862
+ const token = scanner.scan();
863
+ switch (token) {
864
+ case 5 /* Json.SyntaxKind.CommaToken */:
865
+ case 2 /* Json.SyntaxKind.CloseBraceToken */:
866
+ case 4 /* Json.SyntaxKind.CloseBracketToken */:
867
+ case 17 /* Json.SyntaxKind.EOF */:
868
+ return '';
869
+ default:
870
+ return ',';
871
+ }
872
+ }
873
+ findItemAtOffset(node, document, offset) {
874
+ const scanner = Json.createScanner(document.getText(), true);
875
+ const children = node.items;
876
+ for (let i = children.length - 1; i >= 0; i--) {
877
+ const child = children[i];
878
+ if (offset > child.offset + child.length) {
879
+ scanner.setPosition(child.offset + child.length);
880
+ const token = scanner.scan();
881
+ if (token === 5 /* Json.SyntaxKind.CommaToken */ && offset >= scanner.getTokenOffset() + scanner.getTokenLength()) {
882
+ return i + 1;
883
+ }
884
+ return i;
885
+ }
886
+ else if (offset >= child.offset) {
887
+ return i;
888
+ }
889
+ }
890
+ return 0;
891
+ }
892
+ isInComment(document, start, offset) {
893
+ const scanner = Json.createScanner(document.getText(), false);
894
+ scanner.setPosition(start);
895
+ let token = scanner.scan();
896
+ while (token !== 17 /* Json.SyntaxKind.EOF */ && (scanner.getTokenOffset() + scanner.getTokenLength() < offset)) {
897
+ token = scanner.scan();
898
+ }
899
+ return (token === 12 /* Json.SyntaxKind.LineCommentTrivia */ || token === 13 /* Json.SyntaxKind.BlockCommentTrivia */) && scanner.getTokenOffset() <= offset;
900
+ }
901
+ fromMarkup(markupString) {
902
+ if (markupString && this.doesSupportMarkdown()) {
903
+ return {
904
+ kind: MarkupKind.Markdown,
905
+ value: markupString
906
+ };
907
+ }
908
+ return undefined;
909
+ }
910
+ doesSupportMarkdown() {
911
+ if (!isDefined(this.supportsMarkdown)) {
912
+ const completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
913
+ this.supportsMarkdown = completion && completion.completionItem && Array.isArray(completion.completionItem.documentationFormat) && completion.completionItem.documentationFormat.indexOf(MarkupKind.Markdown) !== -1;
914
+ }
915
+ return this.supportsMarkdown;
916
+ }
917
+ doesSupportsCommitCharacters() {
918
+ if (!isDefined(this.supportsCommitCharacters)) {
919
+ const completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
920
+ this.supportsCommitCharacters = completion && completion.completionItem && !!completion.completionItem.commitCharactersSupport;
921
+ }
922
+ return this.supportsCommitCharacters;
923
+ }
924
+ }