getLinks.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  6. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  7. return new (P || (P = Promise))(function (resolve, reject) {
  8. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  9. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  10. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  11. step((generator = generator.apply(thisArg, _arguments || [])).next());
  12. });
  13. };
  14. import { coalesce } from '../../../base/common/arrays.js';
  15. import { CancellationToken } from '../../../base/common/cancellation.js';
  16. import { onUnexpectedExternalError } from '../../../base/common/errors.js';
  17. import { DisposableStore, isDisposable } from '../../../base/common/lifecycle.js';
  18. import { assertType } from '../../../base/common/types.js';
  19. import { URI } from '../../../base/common/uri.js';
  20. import { Range } from '../../common/core/range.js';
  21. import { LinkProviderRegistry } from '../../common/modes.js';
  22. import { IModelService } from '../../common/services/modelService.js';
  23. import { CommandsRegistry } from '../../../platform/commands/common/commands.js';
  24. export class Link {
  25. constructor(link, provider) {
  26. this._link = link;
  27. this._provider = provider;
  28. }
  29. toJSON() {
  30. return {
  31. range: this.range,
  32. url: this.url,
  33. tooltip: this.tooltip
  34. };
  35. }
  36. get range() {
  37. return this._link.range;
  38. }
  39. get url() {
  40. return this._link.url;
  41. }
  42. get tooltip() {
  43. return this._link.tooltip;
  44. }
  45. resolve(token) {
  46. return __awaiter(this, void 0, void 0, function* () {
  47. if (this._link.url) {
  48. return this._link.url;
  49. }
  50. if (typeof this._provider.resolveLink === 'function') {
  51. return Promise.resolve(this._provider.resolveLink(this._link, token)).then(value => {
  52. this._link = value || this._link;
  53. if (this._link.url) {
  54. // recurse
  55. return this.resolve(token);
  56. }
  57. return Promise.reject(new Error('missing'));
  58. });
  59. }
  60. return Promise.reject(new Error('missing'));
  61. });
  62. }
  63. }
  64. export class LinksList {
  65. constructor(tuples) {
  66. this._disposables = new DisposableStore();
  67. let links = [];
  68. for (const [list, provider] of tuples) {
  69. // merge all links
  70. const newLinks = list.links.map(link => new Link(link, provider));
  71. links = LinksList._union(links, newLinks);
  72. // register disposables
  73. if (isDisposable(list)) {
  74. this._disposables.add(list);
  75. }
  76. }
  77. this.links = links;
  78. }
  79. dispose() {
  80. this._disposables.dispose();
  81. this.links.length = 0;
  82. }
  83. static _union(oldLinks, newLinks) {
  84. // reunite oldLinks with newLinks and remove duplicates
  85. let result = [];
  86. let oldIndex;
  87. let oldLen;
  88. let newIndex;
  89. let newLen;
  90. for (oldIndex = 0, newIndex = 0, oldLen = oldLinks.length, newLen = newLinks.length; oldIndex < oldLen && newIndex < newLen;) {
  91. const oldLink = oldLinks[oldIndex];
  92. const newLink = newLinks[newIndex];
  93. if (Range.areIntersectingOrTouching(oldLink.range, newLink.range)) {
  94. // Remove the oldLink
  95. oldIndex++;
  96. continue;
  97. }
  98. const comparisonResult = Range.compareRangesUsingStarts(oldLink.range, newLink.range);
  99. if (comparisonResult < 0) {
  100. // oldLink is before
  101. result.push(oldLink);
  102. oldIndex++;
  103. }
  104. else {
  105. // newLink is before
  106. result.push(newLink);
  107. newIndex++;
  108. }
  109. }
  110. for (; oldIndex < oldLen; oldIndex++) {
  111. result.push(oldLinks[oldIndex]);
  112. }
  113. for (; newIndex < newLen; newIndex++) {
  114. result.push(newLinks[newIndex]);
  115. }
  116. return result;
  117. }
  118. }
  119. export function getLinks(model, token) {
  120. const lists = [];
  121. // ask all providers for links in parallel
  122. const promises = LinkProviderRegistry.ordered(model).reverse().map((provider, i) => {
  123. return Promise.resolve(provider.provideLinks(model, token)).then(result => {
  124. if (result) {
  125. lists[i] = [result, provider];
  126. }
  127. }, onUnexpectedExternalError);
  128. });
  129. return Promise.all(promises).then(() => {
  130. const result = new LinksList(coalesce(lists));
  131. if (!token.isCancellationRequested) {
  132. return result;
  133. }
  134. result.dispose();
  135. return new LinksList([]);
  136. });
  137. }
  138. CommandsRegistry.registerCommand('_executeLinkProvider', (accessor, ...args) => __awaiter(void 0, void 0, void 0, function* () {
  139. let [uri, resolveCount] = args;
  140. assertType(uri instanceof URI);
  141. if (typeof resolveCount !== 'number') {
  142. resolveCount = 0;
  143. }
  144. const model = accessor.get(IModelService).getModel(uri);
  145. if (!model) {
  146. return [];
  147. }
  148. const list = yield getLinks(model, CancellationToken.None);
  149. if (!list) {
  150. return [];
  151. }
  152. // resolve links
  153. for (let i = 0; i < Math.min(resolveCount, list.links.length); i++) {
  154. yield list.links[i].resolve(CancellationToken.None);
  155. }
  156. const result = list.links.slice(0);
  157. list.dispose();
  158. return result;
  159. }));