Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/links/browser/getLinks.ts
3296 views
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
6
import { coalesce } from '../../../../base/common/arrays.js';
7
import { CancellationToken } from '../../../../base/common/cancellation.js';
8
import { onUnexpectedExternalError } from '../../../../base/common/errors.js';
9
import { DisposableStore, isDisposable } from '../../../../base/common/lifecycle.js';
10
import { assertType } from '../../../../base/common/types.js';
11
import { URI } from '../../../../base/common/uri.js';
12
import { IRange, Range } from '../../../common/core/range.js';
13
import { ITextModel } from '../../../common/model.js';
14
import { ILink, ILinksList, LinkProvider } from '../../../common/languages.js';
15
import { IModelService } from '../../../common/services/model.js';
16
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
17
import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js';
18
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
19
20
export class Link implements ILink {
21
22
private _link: ILink;
23
private readonly _provider: LinkProvider;
24
25
constructor(link: ILink, provider: LinkProvider) {
26
this._link = link;
27
this._provider = provider;
28
}
29
30
toJSON(): ILink {
31
return {
32
range: this.range,
33
url: this.url,
34
tooltip: this.tooltip
35
};
36
}
37
38
get range(): IRange {
39
return this._link.range;
40
}
41
42
get url(): URI | string | undefined {
43
return this._link.url;
44
}
45
46
get tooltip(): string | undefined {
47
return this._link.tooltip;
48
}
49
50
async resolve(token: CancellationToken): Promise<URI | string> {
51
if (this._link.url) {
52
return this._link.url;
53
}
54
55
if (typeof this._provider.resolveLink === 'function') {
56
return Promise.resolve(this._provider.resolveLink(this._link, token)).then(value => {
57
this._link = value || this._link;
58
if (this._link.url) {
59
// recurse
60
return this.resolve(token);
61
}
62
63
return Promise.reject(new Error('missing'));
64
});
65
}
66
67
return Promise.reject(new Error('missing'));
68
}
69
}
70
71
export class LinksList {
72
73
static readonly Empty = new LinksList([]);
74
75
readonly links: Link[];
76
77
private readonly _disposables: DisposableStore | undefined = new DisposableStore();
78
79
constructor(tuples: [ILinksList, LinkProvider][]) {
80
81
let links: Link[] = [];
82
for (const [list, provider] of tuples) {
83
// merge all links
84
const newLinks = list.links.map(link => new Link(link, provider));
85
links = LinksList._union(links, newLinks);
86
// register disposables
87
if (isDisposable(list)) {
88
this._disposables ??= new DisposableStore();
89
this._disposables.add(list);
90
}
91
}
92
this.links = links;
93
}
94
95
dispose(): void {
96
this._disposables?.dispose();
97
this.links.length = 0;
98
}
99
100
private static _union(oldLinks: Link[], newLinks: Link[]): Link[] {
101
// reunite oldLinks with newLinks and remove duplicates
102
const result: Link[] = [];
103
let oldIndex: number;
104
let oldLen: number;
105
let newIndex: number;
106
let newLen: number;
107
108
for (oldIndex = 0, newIndex = 0, oldLen = oldLinks.length, newLen = newLinks.length; oldIndex < oldLen && newIndex < newLen;) {
109
const oldLink = oldLinks[oldIndex];
110
const newLink = newLinks[newIndex];
111
112
if (Range.areIntersectingOrTouching(oldLink.range, newLink.range)) {
113
// Remove the oldLink
114
oldIndex++;
115
continue;
116
}
117
118
const comparisonResult = Range.compareRangesUsingStarts(oldLink.range, newLink.range);
119
120
if (comparisonResult < 0) {
121
// oldLink is before
122
result.push(oldLink);
123
oldIndex++;
124
} else {
125
// newLink is before
126
result.push(newLink);
127
newIndex++;
128
}
129
}
130
131
for (; oldIndex < oldLen; oldIndex++) {
132
result.push(oldLinks[oldIndex]);
133
}
134
for (; newIndex < newLen; newIndex++) {
135
result.push(newLinks[newIndex]);
136
}
137
138
return result;
139
}
140
141
}
142
143
export async function getLinks(providers: LanguageFeatureRegistry<LinkProvider>, model: ITextModel, token: CancellationToken): Promise<LinksList> {
144
const lists: [ILinksList, LinkProvider][] = [];
145
146
// ask all providers for links in parallel
147
const promises = providers.ordered(model).reverse().map(async (provider, i) => {
148
try {
149
const result = await provider.provideLinks(model, token);
150
if (result) {
151
lists[i] = [result, provider];
152
}
153
} catch (err) {
154
onUnexpectedExternalError(err);
155
}
156
});
157
158
await Promise.all(promises);
159
160
let res = new LinksList(coalesce(lists));
161
162
if (token.isCancellationRequested) {
163
res.dispose();
164
res = LinksList.Empty;
165
}
166
167
return res;
168
}
169
170
171
CommandsRegistry.registerCommand('_executeLinkProvider', async (accessor, ...args): Promise<ILink[]> => {
172
let [uri, resolveCount] = args;
173
assertType(uri instanceof URI);
174
175
if (typeof resolveCount !== 'number') {
176
resolveCount = 0;
177
}
178
179
const { linkProvider } = accessor.get(ILanguageFeaturesService);
180
const model = accessor.get(IModelService).getModel(uri);
181
if (!model) {
182
return [];
183
}
184
const list = await getLinks(linkProvider, model, CancellationToken.None);
185
if (!list) {
186
return [];
187
}
188
189
// resolve links
190
for (let i = 0; i < Math.min(resolveCount, list.links.length); i++) {
191
await list.links[i].resolve(CancellationToken.None);
192
}
193
194
const result = list.links.slice(0);
195
list.dispose();
196
return result;
197
});
198
199