jsonCompletion.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  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;
  24. }
  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);
  30. if (resolver) {
  31. return resolver;
  32. }
  33. }
  34. }
  35. return this.promiseConstructor.resolve(item);
  36. };
  37. JSONCompletion.prototype.doComplete = function (document, position, doc) {
  38. var _this = this;
  39. var result = {
  40. items: [],
  41. isIncomplete: false
  42. };
  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);
  48. }
  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 === ']') {
  52. // after ] or }
  53. node = node.parent;
  54. }
  55. }
  56. var currentWord = this.getCurrentWord(document, offset);
  57. var overwriteRange;
  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));
  60. }
  61. else {
  62. var overwriteStart = offset - currentWord.length;
  63. if (overwriteStart > 0 && text[overwriteStart - 1] === '"') {
  64. overwriteStart--;
  65. }
  66. overwriteRange = Range.create(document.positionAt(overwriteStart), position);
  67. }
  68. var supportsCommitCharacters = false; //this.doesSupportsCommitCharacters(); disabled for now, waiting for new API: https://github.com/microsoft/vscode/issues/42544
  69. var proposed = {};
  70. var collector = {
  71. add: function (suggestion) {
  72. var label = suggestion.label;
  73. var existing = proposed[label];
  74. if (!existing) {
  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;
  80. }
  81. }
  82. if (overwriteRange && suggestion.insertText !== undefined) {
  83. suggestion.textEdit = TextEdit.replace(overwriteRange, suggestion.insertText);
  84. }
  85. if (supportsCommitCharacters) {
  86. suggestion.commitCharacters = suggestion.kind === CompletionItemKind.Property ? propertyCommitCharacters : valueCommitCharacters;
  87. }
  88. suggestion.label = label;
  89. proposed[label] = suggestion;
  90. result.items.push(suggestion);
  91. }
  92. else {
  93. if (!existing.documentation) {
  94. existing.documentation = suggestion.documentation;
  95. }
  96. if (!existing.detail) {
  97. existing.detail = suggestion.detail;
  98. }
  99. }
  100. },
  101. setAsIncomplete: function () {
  102. result.isIncomplete = true;
  103. },
  104. error: function (message) {
  105. console.error(message);
  106. },
  107. log: function (message) {
  108. console.log(message);
  109. },
  110. getNumberOfProposals: function () {
  111. return result.items.length;
  112. }
  113. };
  114. return this.schemaService.getSchemaForResource(document.uri, doc).then(function (schema) {
  115. var collectionPromises = [];
  116. var addValue = true;
  117. var currentKey = '';
  118. var currentProperty = undefined;
  119. if (node) {
  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);
  126. if (parent) {
  127. node = parent.parent;
  128. }
  129. }
  130. }
  131. }
  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) {
  136. return result;
  137. }
  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('__');
  143. }
  144. });
  145. var separatorAfter_1 = '';
  146. if (addValue) {
  147. separatorAfter_1 = _this.evaluateSeparatorAfter(document, document.offsetAt(overwriteRange.end));
  148. }
  149. if (schema) {
  150. // property proposals with schema
  151. _this.getPropertyCompletions(schema, doc, node, addValue, separatorAfter_1, collector);
  152. }
  153. else {
  154. // property proposals without schema
  155. _this.getSchemaLessPropertyCompletions(doc, node, currentKey, collector);
  156. }
  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);
  162. }
  163. });
  164. if ((!schema && currentWord.length > 0 && text.charAt(offset - currentWord.length - 1) !== '"')) {
  165. collector.add({
  166. kind: CompletionItemKind.Property,
  167. label: _this.getLabelForValue(currentWord),
  168. insertText: _this.getInsertTextForProperty(currentWord, undefined, false, separatorAfter_1),
  169. insertTextFormat: InsertTextFormat.Snippet, documentation: '',
  170. });
  171. collector.setAsIncomplete();
  172. }
  173. }
  174. // proposals for values
  175. var types = {};
  176. if (schema) {
  177. // value proposals with schema
  178. _this.getValueCompletions(schema, doc, node, offset, document, collector, types);
  179. }
  180. else {
  181. // value proposals without schema
  182. _this.getSchemaLessValueCompletions(doc, node, offset, document, collector);
  183. }
  184. if (_this.contributions.length > 0) {
  185. _this.getContributedValueCompletions(doc, node, offset, document, collector, collectionPromises);
  186. }
  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;
  192. }
  193. var separatorAfter = _this.evaluateSeparatorAfter(document, offsetForSeparator);
  194. _this.addFillerValueCompletions(types, separatorAfter, collector);
  195. }
  196. return result;
  197. });
  198. });
  199. };
  200. JSONCompletion.prototype.getPropertyCompletions = function (schema, doc, node, addValue, separatorAfter, collector) {
  201. var _this = this;
  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) {
  210. var proposal = {
  211. kind: CompletionItemKind.Property,
  212. label: key,
  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 || '',
  217. };
  218. if (propertySchema.suggestSortText !== undefined) {
  219. proposal.sortText = propertySchema.suggestSortText;
  220. }
  221. if (proposal.insertText && endsWith(proposal.insertText, "$1" + separatorAfter)) {
  222. proposal.command = {
  223. title: 'Suggest',
  224. command: 'editor.action.triggerSuggest'
  225. };
  226. }
  227. collector.add(proposal);
  228. }
  229. });
  230. }
  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; }
  235. var proposal = {
  236. kind: CompletionItemKind.Property,
  237. label: name,
  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 || '',
  242. };
  243. if (schemaPropertyNames_1.suggestSortText !== undefined) {
  244. proposal.sortText = schemaPropertyNames_1.suggestSortText;
  245. }
  246. if (proposal.insertText && endsWith(proposal.insertText, "$1" + separatorAfter)) {
  247. proposal.command = {
  248. title: 'Suggest',
  249. command: 'editor.action.triggerSuggest'
  250. };
  251. }
  252. collector.add(proposal);
  253. };
  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]);
  259. }
  260. else if (schemaPropertyNames_1.enumDescriptions && i < schemaPropertyNames_1.enumDescriptions.length) {
  261. enumDescription = schemaPropertyNames_1.enumDescriptions[i];
  262. }
  263. propertyNameCompletionItem(schemaPropertyNames_1.enum[i], enumDescription);
  264. }
  265. }
  266. if (schemaPropertyNames_1.const) {
  267. propertyNameCompletionItem(schemaPropertyNames_1.const);
  268. }
  269. }
  270. }
  271. });
  272. };
  273. JSONCompletion.prototype.getSchemaLessPropertyCompletions = function (doc, node, currentKey, collector) {
  274. var _this = this;
  275. var collectCompletionsForSimilarObject = function (obj) {
  276. obj.properties.forEach(function (p) {
  277. var key = p.keyNode.value;
  278. collector.add({
  279. kind: CompletionItemKind.Property,
  280. label: key,
  281. insertText: _this.getInsertTextForValue(key, ''),
  282. insertTextFormat: InsertTextFormat.Snippet,
  283. filterText: _this.getFilterTextForValue(key),
  284. documentation: ''
  285. });
  286. });
  287. };
  288. if (node.parent) {
  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);
  295. }
  296. return true;
  297. });
  298. }
  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);
  304. }
  305. });
  306. }
  307. }
  308. else if (node.type === 'object') {
  309. collector.add({
  310. kind: CompletionItemKind.Property,
  311. label: '$schema',
  312. insertText: this.getInsertTextForProperty('$schema', undefined, true, ''),
  313. insertTextFormat: InsertTextFormat.Snippet, documentation: '',
  314. filterText: this.getFilterTextForValue("$schema")
  315. });
  316. }
  317. };
  318. JSONCompletion.prototype.getSchemaLessValueCompletions = function (doc, node, offset, document, collector) {
  319. var _this = this;
  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;
  323. node = node.parent;
  324. }
  325. if (!node) {
  326. collector.add({
  327. kind: this.getSuggestionKind('object'),
  328. label: 'Empty object',
  329. insertText: this.getInsertTextForValue({}, ''),
  330. insertTextFormat: InsertTextFormat.Snippet,
  331. documentation: ''
  332. });
  333. collector.add({
  334. kind: this.getSuggestionKind('array'),
  335. label: 'Empty array',
  336. insertText: this.getInsertTextForValue([], ''),
  337. insertTextFormat: InsertTextFormat.Snippet,
  338. documentation: ''
  339. });
  340. return;
  341. }
  342. var separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
  343. var collectSuggestionsForValues = function (value) {
  344. if (value.parent && !Parser.contains(value.parent, offset, true)) {
  345. collector.add({
  346. kind: _this.getSuggestionKind(value.type),
  347. label: _this.getLabelTextForMatchingNode(value, document),
  348. insertText: _this.getInsertTextForMatchingNode(value, document, separatorAfter),
  349. insertTextFormat: InsertTextFormat.Snippet, documentation: ''
  350. });
  351. }
  352. if (value.type === 'boolean') {
  353. _this.addBooleanValueCompletion(!value.value, separatorAfter, collector);
  354. }
  355. };
  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')) {
  360. return;
  361. }
  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);
  367. }
  368. return true;
  369. });
  370. if (parentKey_2 === '$schema' && node.parent && !node.parent.parent) {
  371. this.addDollarSchemaCompletions(separatorAfter, collector);
  372. }
  373. }
  374. }
  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);
  382. }
  383. return true;
  384. });
  385. }
  386. else {
  387. // suggest items in the same array
  388. node.items.forEach(collectSuggestionsForValues);
  389. }
  390. }
  391. };
  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;
  398. valueNode = node;
  399. node = node.parent;
  400. }
  401. if (!node) {
  402. this.addSchemaValueCompletions(schema.schema, '', collector, types);
  403. return;
  404. }
  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
  409. }
  410. parentKey = node.keyNode.value;
  411. node = node.parent;
  412. }
  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);
  424. }
  425. }
  426. else {
  427. this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types);
  428. }
  429. }
  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);
  437. }
  438. }
  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);
  447. }
  448. }
  449. }
  450. if (s.schema.additionalProperties && !propertyMatched) {
  451. var propertySchema = s.schema.additionalProperties;
  452. this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
  453. }
  454. }
  455. }
  456. }
  457. if (parentKey === '$schema' && !node.parent) {
  458. this.addDollarSchemaCompletions(separatorAfter, collector);
  459. }
  460. if (types['boolean']) {
  461. this.addBooleanValueCompletion(true, separatorAfter, collector);
  462. this.addBooleanValueCompletion(false, separatorAfter, collector);
  463. }
  464. if (types['null']) {
  465. this.addNullValueCompletion(separatorAfter, collector);
  466. }
  467. }
  468. };
  469. JSONCompletion.prototype.getContributedValueCompletions = function (doc, node, offset, document, collector, collectionPromises) {
  470. if (!node) {
  471. this.contributions.forEach(function (contribution) {
  472. var collectPromise = contribution.collectDefaultCompletions(document.uri, collector);
  473. if (collectPromise) {
  474. collectionPromises.push(collectPromise);
  475. }
  476. });
  477. }
  478. else {
  479. if (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null') {
  480. node = node.parent;
  481. }
  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);
  491. }
  492. });
  493. }
  494. }
  495. }
  496. };
  497. JSONCompletion.prototype.addSchemaValueCompletions = function (schema, separatorAfter, collector, types) {
  498. var _this = this;
  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); });
  505. }
  506. if (Array.isArray(schema.anyOf)) {
  507. schema.anyOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
  508. }
  509. if (Array.isArray(schema.oneOf)) {
  510. schema.oneOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
  511. }
  512. }
  513. };
  514. JSONCompletion.prototype.addDefaultValueCompletions = function (schema, separatorAfter, collector, arrayDepth) {
  515. var _this = this;
  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--) {
  522. value = [value];
  523. type = 'array';
  524. }
  525. collector.add({
  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')
  531. });
  532. hasProposals = true;
  533. }
  534. if (Array.isArray(schema.examples)) {
  535. schema.examples.forEach(function (example) {
  536. var type = schema.type;
  537. var value = example;
  538. for (var i = arrayDepth; i > 0; i--) {
  539. value = [value];
  540. type = 'array';
  541. }
  542. collector.add({
  543. kind: _this.getSuggestionKind(type),
  544. label: _this.getLabelForValue(value),
  545. insertText: _this.getInsertTextForValue(value, separatorAfter),
  546. insertTextFormat: InsertTextFormat.Snippet
  547. });
  548. hasProposals = true;
  549. });
  550. }
  551. if (Array.isArray(schema.defaultSnippets)) {
  552. schema.defaultSnippets.forEach(function (s) {
  553. var type = schema.type;
  554. var value = s.body;
  555. var label = s.label;
  556. var insertText;
  557. var filterText;
  558. if (isDefined(value)) {
  559. var type_1 = schema.type;
  560. for (var i = arrayDepth; i > 0; i--) {
  561. value = [value];
  562. type_1 = 'array';
  563. }
  564. insertText = _this.getInsertTextForSnippetValue(value, separatorAfter);
  565. filterText = _this.getFilterTextForSnippetValue(value);
  566. label = label || _this.getLabelForSnippetValue(value);
  567. }
  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 + ']';
  573. indent += '\t';
  574. type = 'array';
  575. }
  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
  579. }
  580. else {
  581. return;
  582. }
  583. collector.add({
  584. kind: _this.getSuggestionKind(type),
  585. label: label,
  586. documentation: _this.fromMarkup(s.markdownDescription) || s.description,
  587. insertText: insertText,
  588. insertTextFormat: InsertTextFormat.Snippet,
  589. filterText: filterText
  590. });
  591. hasProposals = true;
  592. });
  593. }
  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);
  596. }
  597. };
  598. JSONCompletion.prototype.addEnumValueCompletions = function (schema, separatorAfter, collector) {
  599. if (isDefined(schema.const)) {
  600. collector.add({
  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
  606. });
  607. }
  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]);
  614. }
  615. else if (schema.enumDescriptions && i < schema.enumDescriptions.length) {
  616. documentation = schema.enumDescriptions[i];
  617. }
  618. collector.add({
  619. kind: this.getSuggestionKind(schema.type),
  620. label: this.getLabelForValue(enm),
  621. insertText: this.getInsertTextForValue(enm, separatorAfter),
  622. insertTextFormat: InsertTextFormat.Snippet,
  623. documentation: documentation
  624. });
  625. }
  626. }
  627. };
  628. JSONCompletion.prototype.collectTypes = function (schema, types) {
  629. if (Array.isArray(schema.enum) || isDefined(schema.const)) {
  630. return;
  631. }
  632. var type = schema.type;
  633. if (Array.isArray(type)) {
  634. type.forEach(function (t) { return types[t] = true; });
  635. }
  636. else if (type) {
  637. types[type] = true;
  638. }
  639. };
  640. JSONCompletion.prototype.addFillerValueCompletions = function (types, separatorAfter, collector) {
  641. if (types['object']) {
  642. collector.add({
  643. kind: this.getSuggestionKind('object'),
  644. label: '{}',
  645. insertText: this.getInsertTextForGuessedValue({}, separatorAfter),
  646. insertTextFormat: InsertTextFormat.Snippet,
  647. detail: localize('defaults.object', 'New object'),
  648. documentation: ''
  649. });
  650. }
  651. if (types['array']) {
  652. collector.add({
  653. kind: this.getSuggestionKind('array'),
  654. label: '[]',
  655. insertText: this.getInsertTextForGuessedValue([], separatorAfter),
  656. insertTextFormat: InsertTextFormat.Snippet,
  657. detail: localize('defaults.array', 'New array'),
  658. documentation: ''
  659. });
  660. }
  661. };
  662. JSONCompletion.prototype.addBooleanValueCompletion = function (value, separatorAfter, collector) {
  663. collector.add({
  664. kind: this.getSuggestionKind('boolean'),
  665. label: value ? 'true' : 'false',
  666. insertText: this.getInsertTextForValue(value, separatorAfter),
  667. insertTextFormat: InsertTextFormat.Snippet,
  668. documentation: ''
  669. });
  670. };
  671. JSONCompletion.prototype.addNullValueCompletion = function (separatorAfter, collector) {
  672. collector.add({
  673. kind: this.getSuggestionKind('null'),
  674. label: 'null',
  675. insertText: 'null' + separatorAfter,
  676. insertTextFormat: InsertTextFormat.Snippet,
  677. documentation: ''
  678. });
  679. };
  680. JSONCompletion.prototype.addDollarSchemaCompletions = function (separatorAfter, collector) {
  681. var _this = this;
  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: ''
  689. }); });
  690. };
  691. JSONCompletion.prototype.getLabelForValue = function (value) {
  692. return JSON.stringify(value);
  693. };
  694. JSONCompletion.prototype.getFilterTextForValue = function (value) {
  695. return JSON.stringify(value);
  696. };
  697. JSONCompletion.prototype.getFilterTextForSnippetValue = function (value) {
  698. return JSON.stringify(value).replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
  699. };
  700. JSONCompletion.prototype.getLabelForSnippetValue = function (value) {
  701. var label = JSON.stringify(value);
  702. return label.replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
  703. };
  704. JSONCompletion.prototype.getInsertTextForPlainText = function (text) {
  705. return text.replace(/[\\\$\}]/g, '\\$&'); // escape $, \ and }
  706. };
  707. JSONCompletion.prototype.getInsertTextForValue = function (value, separatorAfter) {
  708. var text = JSON.stringify(value, null, '\t');
  709. if (text === '{}') {
  710. return '{$1}' + separatorAfter;
  711. }
  712. else if (text === '[]') {
  713. return '[$1]' + separatorAfter;
  714. }
  715. return this.getInsertTextForPlainText(text + separatorAfter);
  716. };
  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);
  722. }
  723. }
  724. return JSON.stringify(value);
  725. };
  726. return stringifyObject(value, '', replacer) + separatorAfter;
  727. };
  728. JSONCompletion.prototype.getInsertTextForGuessedValue = function (value, separatorAfter) {
  729. switch (typeof value) {
  730. case 'object':
  731. if (value === null) {
  732. return '${1:null}' + separatorAfter;
  733. }
  734. return this.getInsertTextForValue(value, separatorAfter);
  735. case 'string':
  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;
  740. case 'number':
  741. case 'boolean':
  742. return '${1:' + JSON.stringify(value) + '}' + separatorAfter;
  743. }
  744. return this.getInsertTextForValue(value, separatorAfter);
  745. };
  746. JSONCompletion.prototype.getSuggestionKind = function (type) {
  747. if (Array.isArray(type)) {
  748. var array = type;
  749. type = array.length > 0 ? array[0] : undefined;
  750. }
  751. if (!type) {
  752. return CompletionItemKind.Value;
  753. }
  754. switch (type) {
  755. case 'string': return CompletionItemKind.Value;
  756. case 'object': return CompletionItemKind.Module;
  757. case 'property': return CompletionItemKind.Property;
  758. default: return CompletionItemKind.Value;
  759. }
  760. };
  761. JSONCompletion.prototype.getLabelTextForMatchingNode = function (node, document) {
  762. switch (node.type) {
  763. case 'array':
  764. return '[]';
  765. case 'object':
  766. return '{}';
  767. default:
  768. var content = document.getText().substr(node.offset, node.length);
  769. return content;
  770. }
  771. };
  772. JSONCompletion.prototype.getInsertTextForMatchingNode = function (node, document, separatorAfter) {
  773. switch (node.type) {
  774. case 'array':
  775. return this.getInsertTextForValue([], separatorAfter);
  776. case 'object':
  777. return this.getInsertTextForValue({}, separatorAfter);
  778. default:
  779. var content = document.getText().substr(node.offset, node.length) + separatorAfter;
  780. return this.getInsertTextForPlainText(content);
  781. }
  782. };
  783. JSONCompletion.prototype.getInsertTextForProperty = function (key, propertySchema, addValue, separatorAfter) {
  784. var propertyText = this.getInsertTextForValue(key, '');
  785. if (!addValue) {
  786. return propertyText;
  787. }
  788. var resultText = propertyText + ': ';
  789. var value;
  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, '');
  797. }
  798. }
  799. nValueProposals += propertySchema.defaultSnippets.length;
  800. }
  801. if (propertySchema.enum) {
  802. if (!value && propertySchema.enum.length === 1) {
  803. value = this.getInsertTextForGuessedValue(propertySchema.enum[0], '');
  804. }
  805. nValueProposals += propertySchema.enum.length;
  806. }
  807. if (isDefined(propertySchema.default)) {
  808. if (!value) {
  809. value = this.getInsertTextForGuessedValue(propertySchema.default, '');
  810. }
  811. nValueProposals++;
  812. }
  813. if (Array.isArray(propertySchema.examples) && propertySchema.examples.length) {
  814. if (!value) {
  815. value = this.getInsertTextForGuessedValue(propertySchema.examples[0], '');
  816. }
  817. nValueProposals += propertySchema.examples.length;
  818. }
  819. if (nValueProposals === 0) {
  820. var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
  821. if (!type) {
  822. if (propertySchema.properties) {
  823. type = 'object';
  824. }
  825. else if (propertySchema.items) {
  826. type = 'array';
  827. }
  828. }
  829. switch (type) {
  830. case 'boolean':
  831. value = '$1';
  832. break;
  833. case 'string':
  834. value = '"$1"';
  835. break;
  836. case 'object':
  837. value = '{$1}';
  838. break;
  839. case 'array':
  840. value = '[$1]';
  841. break;
  842. case 'number':
  843. case 'integer':
  844. value = '${1:0}';
  845. break;
  846. case 'null':
  847. value = '${1:null}';
  848. break;
  849. default:
  850. return propertyText;
  851. }
  852. }
  853. }
  854. if (!value || nValueProposals > 1) {
  855. value = '$1';
  856. }
  857. return resultText + value + separatorAfter;
  858. };
  859. JSONCompletion.prototype.getCurrentWord = function (document, offset) {
  860. var i = offset - 1;
  861. var text = document.getText();
  862. while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
  863. i--;
  864. }
  865. return text.substring(i + 1, offset);
  866. };
  867. JSONCompletion.prototype.evaluateSeparatorAfter = function (document, offset) {
  868. var scanner = Json.createScanner(document.getText(), true);
  869. scanner.setPosition(offset);
  870. var token = scanner.scan();
  871. switch (token) {
  872. case 5 /* CommaToken */:
  873. case 2 /* CloseBraceToken */:
  874. case 4 /* CloseBracketToken */:
  875. case 17 /* EOF */:
  876. return '';
  877. default:
  878. return ',';
  879. }
  880. };
  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()) {
  890. return i + 1;
  891. }
  892. return i;
  893. }
  894. else if (offset >= child.offset) {
  895. return i;
  896. }
  897. }
  898. return 0;
  899. };
  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();
  906. }
  907. return (token === 12 /* LineCommentTrivia */ || token === 13 /* BlockCommentTrivia */) && scanner.getTokenOffset() <= offset;
  908. };
  909. JSONCompletion.prototype.fromMarkup = function (markupString) {
  910. if (markupString && this.doesSupportMarkdown()) {
  911. return {
  912. kind: MarkupKind.Markdown,
  913. value: markupString
  914. };
  915. }
  916. return undefined;
  917. };
  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;
  922. }
  923. return this.supportsMarkdown;
  924. };
  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;
  929. }
  930. return this.supportsCommitCharacters;
  931. };
  932. return JSONCompletion;
  933. }());
  934. export { JSONCompletion };