src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts

423 LOC · 262 covered · 161 uncovered · 63 ranges · 71 concepts · 11 introducers · 43 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- extensionDescriptionRegistry.ts ×37
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 { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';
7 > import { Emitter } from '../../../../base/common/event.js';
8 > import * as path from '../../../../base/common/path.js';
9 > import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
10 > import { promiseWithResolvers } from '../../../../base/common/async.js';
11 >
12 > export class DeltaExtensionsResult {
13 > constructor(
14 > public readonly versionId: number, extensionDescriptionRegistry.ts ×11
15 > public readonly removedDueToLooping: IExtensionDescription[]
16 > ) { }
18 >
19 > export interface IReadOnlyExtensionDescriptionRegistry {
20 > containsActivationEvent(activationEvent: string): boolean;
21 > containsExtension(extensionId: ExtensionIdentifier): boolean;
22 > getExtensionDescriptionsForActivationEvent(activationEvent: string): IExtensionDescription[];
23 > getAllExtensionDescriptions(): IExtensionDescription[];
24 > getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined;
25 > getExtensionDescriptionByUUID(uuid: string): IExtensionDescription | undefined;
26 > getExtensionDescriptionByIdOrUUID(extensionId: ExtensionIdentifier | string, uuid: string | undefined): IExtensionDescription | undefined;
27 > }
28 >
29 > export class ExtensionDescriptionRegistry extends Disposable implements IReadOnlyExtensionDescriptionRegistry {
30 >
31 > public static isHostExtension(extensionId: ExtensionIdentifier | string, myRegistry: ExtensionDescriptionRegistry, globalRegistry: ExtensionDescriptionRegistry): boolean {
32 > if (myRegistry.getExtensionDescription(extensionId)) {
33 > // I have this extension
34 > return false;
35 > }
36 > const extensionDescription = globalRegistry.getExtensionDescription(extensionId); extensionDescriptionRegistry.ts ×1
37 > if (!extensionDescription) {
38 > // unknown extension extensionDescriptionRegistry.ts ×1
39 > return false;
40 > }
41 > if ((extensionDescription.main || extensionDescription.browser) && extensionDescription.api === 'none') { extensionDescriptionRegistry.ts ×37
43 > }
46 >
47 > private readonly _onDidChange = this._register(new Emitter<void>());
48 > public readonly onDidChange = this._onDidChange.event;
49 >
50 > private _versionId: number = 0;
51 > private _extensionDescriptions: IExtensionDescription[];
52 > private _extensionsMap!: ExtensionIdentifierMap<IExtensionDescription>;
53 > private _extensionsArr!: IExtensionDescription[];
54 > private _activationMap!: Map<string, IExtensionDescription[]>;
55 >
56 > constructor(
57 > private readonly _activationEventsReader: IActivationEventsReader, extensionDescriptionRegistry.ts ×3
58 > extensionDescriptions: IExtensionDescription[]
59 > ) {
60 > super();
61 > this._extensionDescriptions = extensionDescriptions;
62 > this._initialize();
63 > }
65 > private _initialize(): void {
66 > // Ensure extensions are stored in the order: builtin, user, under development extensionDescriptionRegistry.ts ×3
67 > this._extensionDescriptions.sort(extensionCmp);
68 >
69 > this._extensionsMap = new ExtensionIdentifierMap<IExtensionDescription>();
70 > this._extensionsArr = [];
71 > this._activationMap = new Map<string, IExtensionDescription[]>();
72 >
73 > for (const extensionDescription of this._extensionDescriptions) {
74 > if (this._extensionsMap.has(extensionDescription.identifier)) { extensionDescriptionRegistry.ts ×2
75 // No overwriting allowed!
76 console.error('Extension `' + extensionDescription.identifier.value + '` is already registered');
77 continue;
78 }
80 > this._extensionsMap.set(extensionDescription.identifier, extensionDescription);
81 > this._extensionsArr.push(extensionDescription);
82 >
83 > const activationEvents = this._activationEventsReader.readActivationEvents(extensionDescription);
84 > for (const activationEvent of activationEvents) {
85 > if (!this._activationMap.has(activationEvent)) {
86 > this._activationMap.set(activationEvent, []);
87 > }
88 > this._activationMap.get(activationEvent)!.push(extensionDescription);
89 > }
90 > }
93 > public set(extensionDescriptions: IExtensionDescription[]): { versionId: number } {
94 this._extensionDescriptions = extensionDescriptions;
95 this._initialize();
96 this._versionId++;
97 this._onDidChange.fire(undefined);
98 return {
99 versionId: this._versionId
100 };
101 }
103 > public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): DeltaExtensionsResult {
104 > // It is possible that an extension is removed, only to be added again at a different version extensionDescriptionRegistry.ts ×11
105 > // so we will first handle removals
106 > this._extensionDescriptions = removeExtensions(this._extensionDescriptions, toRemove);
107 >
108 > // Then, handle the extensions to add
109 > this._extensionDescriptions = this._extensionDescriptions.concat(toAdd);
110 >
111 > // Immediately remove looping extensions!
112 > const looping = ExtensionDescriptionRegistry._findLoopingExtensions(this._extensionDescriptions);
113 > this._extensionDescriptions = removeExtensions(this._extensionDescriptions, looping.map(ext => ext.identifier));
114 >
115 > this._initialize();
116 > this._versionId++;
117 > this._onDidChange.fire(undefined);
118 > return new DeltaExtensionsResult(this._versionId, looping);
119 > }
121 > private static _findLoopingExtensions(extensionDescriptions: IExtensionDescription[]): IExtensionDescription[] {
122 > const G = new class { extensionDescriptionRegistry.ts ×11
123 >
124 > private _arcs = new Map<string, string[]>();
125 > private _nodesSet = new Set<string>();
126 > private _nodesArr: string[] = [];
127 >
128 > addNode(id: string): void {
129 if (!this._nodesSet.has(id)) {
130 this._nodesSet.add(id);
131 this._nodesArr.push(id);
132 }
133 }
135 > addArc(from: string, to: string): void {
136 this.addNode(from);
137 this.addNode(to);
138 if (this._arcs.has(from)) {
139 this._arcs.get(from)!.push(to);
140 } else {
141 this._arcs.set(from, [to]);
142 }
143 }
145 > getArcs(id: string): string[] {
146 if (this._arcs.has(id)) {
147 return this._arcs.get(id)!;
148 }
149 return [];
150 }
152 > hasOnlyGoodArcs(id: string, good: Set<string>): boolean {
153 const dependencies = G.getArcs(id);
154 for (let i = 0; i < dependencies.length; i++) {
155 if (!good.has(dependencies[i])) {
156 return false;
157 }
158 }
159 return true;
160 }
162 > getNodes(): string[] {
163 > return this._nodesArr;
164 > }
165 > };
166 >
167 > const descs = new ExtensionIdentifierMap<IExtensionDescription>();
168 > for (const extensionDescription of extensionDescriptions) {
169 > descs.set(extensionDescription.identifier, extensionDescription);
170 > if (extensionDescription.extensionDependencies) {
171 > for (const depId of extensionDescription.extensionDependencies) {
172 G.addArc(ExtensionIdentifier.toKey(extensionDescription.identifier), ExtensionIdentifier.toKey(depId));
173 }
175 > }
176 >
177 > // initialize with all extensions with no dependencies.
178 > const good = new Set<string>();
179 > G.getNodes().filter(id => G.getArcs(id).length === 0).forEach(id => good.add(id));
180 >
181 > // all other extensions will be processed below.
182 > const nodes = G.getNodes().filter(id => !good.has(id));
183 >
184 > let madeProgress: boolean;
185 > do {
186 > madeProgress = false;
187 >
188 > // find one extension which has only good deps
189 > for (let i = 0; i < nodes.length; i++) {
190 const id = nodes[i];
191
192 if (G.hasOnlyGoodArcs(id, good)) {
193 nodes.splice(i, 1);
194 i--;
195 good.add(id);
196 madeProgress = true;
197 }
198 }
199 > } while (madeProgress); extensionDescriptionRegistry.ts ×11
200 >
201 > // The remaining nodes are bad and have loops
202 > return nodes.map(id => descs.get(id)!);
203 > }
205 > public containsActivationEvent(activationEvent: string): boolean {
206 return this._activationMap.has(activationEvent);
207 }
209 > public containsExtension(extensionId: ExtensionIdentifier): boolean {
210 return this._extensionsMap.has(extensionId);
211 }
213 > public getExtensionDescriptionsForActivationEvent(activationEvent: string): IExtensionDescription[] {
214 > const extensions = this._activationMap.get(activationEvent); extHostExtensionActivator.ts ×20
215 > return extensions ? extensions.slice(0) : [];
216 > }
218 > public getAllExtensionDescriptions(): IExtensionDescription[] {
219 > return this._extensionsArr.slice(0); extensionDescriptionRegistry.ts ×11
220 > }
222 > public getSnapshot(): ExtensionDescriptionRegistrySnapshot {
223 return new ExtensionDescriptionRegistrySnapshot(
224 this._versionId,
225 this.getAllExtensionDescriptions()
226 );
227 }
229 > public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined {
230 > const extension = this._extensionsMap.get(extensionId); extHostExtensionActivator.ts ×3
231 > return extension ? extension : undefined;
232 > }
234 > public getExtensionDescriptionByUUID(uuid: string): IExtensionDescription | undefined {
235 for (const extensionDescription of this._extensionsArr) {
236 if (extensionDescription.uuid === uuid) {
237 return extensionDescription;
238 }
239 }
240 return undefined;
241 }
243 > public getExtensionDescriptionByIdOrUUID(extensionId: ExtensionIdentifier | string, uuid: string | undefined): IExtensionDescription | undefined {
244 return (
245 this.getExtensionDescription(extensionId)
246 ?? (uuid ? this.getExtensionDescriptionByUUID(uuid) : undefined)
247 );
248 }
250 >
251 > export class ExtensionDescriptionRegistrySnapshot {
252 > constructor(
253 public readonly versionId: number,
254 public readonly extensions: readonly IExtensionDescription[]
255 ) { }
257 >
258 > export interface IActivationEventsReader {
259 > readActivationEvents(extensionDescription: IExtensionDescription): string[];
260 > }
261 >
262 > export class LockableExtensionDescriptionRegistry implements IReadOnlyExtensionDescriptionRegistry {
263 >
264 > private readonly _actual: ExtensionDescriptionRegistry;
265 > private readonly _lock = new Lock();
266 >
267 > constructor(activationEventsReader: IActivationEventsReader) {
268 this._actual = new ExtensionDescriptionRegistry(activationEventsReader, []);
269 }
271 > public async acquireLock(customerName: string): Promise<ExtensionDescriptionRegistryLock> {
272 const lock = await this._lock.acquire(customerName);
273 return new ExtensionDescriptionRegistryLock(this, lock);
274 }
276 > public deltaExtensions(acquiredLock: ExtensionDescriptionRegistryLock, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): DeltaExtensionsResult {
277 if (!acquiredLock.isAcquiredFor(this)) {
278 throw new Error('Lock is not held');
279 }
280 return this._actual.deltaExtensions(toAdd, toRemove);
281 }
283 > public containsActivationEvent(activationEvent: string): boolean {
284 return this._actual.containsActivationEvent(activationEvent);
285 }
286 > public containsExtension(extensionId: ExtensionIdentifier): boolean { extensionDescriptionRegistry.ts ×37
287 return this._actual.containsExtension(extensionId);
288 }
289 > public getExtensionDescriptionsForActivationEvent(activationEvent: string): IExtensionDescription[] { extensionDescriptionRegistry.ts ×37
290 return this._actual.getExtensionDescriptionsForActivationEvent(activationEvent);
291 }
292 > public getAllExtensionDescriptions(): IExtensionDescription[] { extensionDescriptionRegistry.ts ×37
293 return this._actual.getAllExtensionDescriptions();
294 }
295 > public getSnapshot(): ExtensionDescriptionRegistrySnapshot { extensionDescriptionRegistry.ts ×37
296 return this._actual.getSnapshot();
297 }
298 > public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined { extensionDescriptionRegistry.ts ×37
299 return this._actual.getExtensionDescription(extensionId);
300 }
301 > public getExtensionDescriptionByUUID(uuid: string): IExtensionDescription | undefined { extensionDescriptionRegistry.ts ×37
302 return this._actual.getExtensionDescriptionByUUID(uuid);
303 }
304 > public getExtensionDescriptionByIdOrUUID(extensionId: ExtensionIdentifier | string, uuid: string | undefined): IExtensionDescription | undefined { extensionDescriptionRegistry.ts ×37
305 return this._actual.getExtensionDescriptionByIdOrUUID(extensionId, uuid);
306 }
308 >
309 > export class ExtensionDescriptionRegistryLock extends Disposable {
310 >
311 > private _isDisposed = false;
312 >
313 > constructor(
314 private readonly _registry: LockableExtensionDescriptionRegistry,
315 lock: IDisposable
316 ) {
317 super();
318 this._register(lock);
319 }
321 > public isAcquiredFor(registry: LockableExtensionDescriptionRegistry): boolean {
322 return !this._isDisposed && this._registry === registry;
323 }
325 >
326 > class LockCustomer {
327 > public readonly promise: Promise<IDisposable>;
328 > private readonly _resolve: (value: IDisposable) => void;
329 >
330 > constructor(
331 public readonly name: string
332 ) {
333 const withResolvers = promiseWithResolvers<IDisposable>();
334 this.promise = withResolvers.promise;
335 this._resolve = withResolvers.resolve;
336 }
338 > resolve(value: IDisposable): void {
339 this._resolve(value);
340 }
342 >
343 class Lock {
344 private readonly _pendingCustomers: LockCustomer[] = [];
345 private _isLocked = false;
347 > public async acquire(customerName: string): Promise<IDisposable> {
348 const customer = new LockCustomer(customerName);
349 this._pendingCustomers.push(customer);
350 this._advance();
351 return customer.promise;
352 }
354 > private _advance(): void {
355 if (this._isLocked) {
356 // cannot advance yet
357 return;
358 }
359 if (this._pendingCustomers.length === 0) {
360 // no more waiting customers
361 return;
362 }
363
364 const customer = this._pendingCustomers.shift()!;
365
366 this._isLocked = true;
367 let customerHoldsLock = true;
368
369 const logLongRunningCustomerTimeout = setTimeout(() => {
370 if (customerHoldsLock) {
371 console.warn(`The customer named ${customer.name} has been holding on to the lock for 30s. This might be a problem.`);
372 }
373 }, 30 * 1000 /* 30 seconds */);
374
375 const releaseLock = () => {
376 if (!customerHoldsLock) {
377 return;
378 }
379 clearTimeout(logLongRunningCustomerTimeout);
380 customerHoldsLock = false;
381 this._isLocked = false;
382 this._advance();
383 };
384
385 customer.resolve(toDisposable(releaseLock));
386 }
388 >
389 > const enum SortBucket {
390 > Builtin = 0,
391 > User = 1,
392 > Dev = 2
393 > }
394 >
395 > /**
396 > * Ensure that:
397 > * - first are builtin extensions
398 > * - second are user extensions
399 > * - third are extensions under development
400 > *
401 > * In each bucket, extensions must be sorted alphabetically by their folder name.
402 > */
403 > function extensionCmp(a: IExtensionDescription, b: IExtensionDescription): number { extensionDescriptionRegistry.ts ×4
404 > const aSortBucket = (a.isBuiltin ? SortBucket.Builtin : a.isUnderDevelopment ? SortBucket.Dev : SortBucket.User);
405 > const bSortBucket = (b.isBuiltin ? SortBucket.Builtin : b.isUnderDevelopment ? SortBucket.Dev : SortBucket.User);
406 > if (aSortBucket !== bSortBucket) {
407 return aSortBucket - bSortBucket;
408 }
409 > const aLastSegment = path.posix.basename(a.extensionLocation.path); extensionDescriptionRegistry.ts ×4
410 > const bLastSegment = path.posix.basename(b.extensionLocation.path);
411 > if (aLastSegment < bLastSegment) {
412 return -1;
413 }
414 > if (aLastSegment > bLastSegment) { extensionDescriptionRegistry.ts ×4
415 return 1;
416 }
418 > }
420 > function removeExtensions(arr: IExtensionDescription[], toRemove: ExtensionIdentifier[]): IExtensionDescription[] { extensionDescriptionRegistry.ts ×11
421 > const toRemoveSet = new ExtensionIdentifierSet(toRemove);
422 > return arr.filter(extension => !toRemoveSet.has(extension.identifier));
423 > }