replica-omnisciente/dirac/evals/opencode/opencode_refactor_extensionswb_service

1924 lines
77 KiB
Text

diff --git a/src/vs/workbench/contrib/extensions/browser/extension.ts b/src/vs/workbench/contrib/extensions/browser/extension.ts
new file mode 100644
index 00000000000..2a07e17025b
--- /dev/null
+++ b/src/vs/workbench/contrib/extensions/browser/extension.ts
@@ -0,0 +1,523 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as nls from '../../../../nls.js';
+import * as semver from '../../../../base/common/semver/semver.js';
+import { URI } from '../../../../base/common/uri.js';
+import * as resources from '../../../../base/common/resources.js';
+import { CancellationToken } from '../../../../base/common/cancellation.js';
+import { FileAccess } from '../../../../base/common/network.js';
+import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
+import { ILogService } from '../../../../platform/log/common/log.js';
+import { IFileService } from '../../../../platform/files/common/files.js';
+import { IProductService } from '../../../../platform/product/common/productService.js';
+import {
+ IExtensionGalleryService, ILocalExtension, IGalleryExtension,
+ IExtensionsControlManifest, IDeprecationInfo, MaliciousExtensionInfo
+} from '../../../../platform/extensionManagement/common/extensionManagement.js';
+import { IExtensionManagementServer, IResourceExtension, EnablementState } from '../../../services/extensionManagement/common/extensionManagement.js';
+import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, findMatchingMaliciousEntry } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
+import { IExtension, ExtensionState, ExtensionRuntimeState } from '../common/extensions.js';
+import { IExtensionManifest, ExtensionType, IExtensionIdentifier, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';
+import { ShowCurrentReleaseNotesActionId } from '../../update/common/update.js';
+
+export interface IExtensionStateProvider<T> {
+ (extension: Extension): T;
+}
+
+export class Extension implements IExtension {
+
+ public enablementState: EnablementState = EnablementState.EnabledGlobally;
+
+ private galleryResourcesCache = new Map<string, any>();
+
+ private _missingFromGallery: boolean | undefined;
+
+ constructor(
+ private stateProvider: IExtensionStateProvider<ExtensionState>,
+ private runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,
+ public readonly server: IExtensionManagementServer | undefined,
+ public local: ILocalExtension | undefined,
+ private _gallery: IGalleryExtension | undefined,
+ private readonly resourceExtensionInfo: { resourceExtension: IResourceExtension; isWorkspaceScoped: boolean } | undefined,
+ @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
+ @ITelemetryService private readonly telemetryService: ITelemetryService,
+ @ILogService private readonly logService: ILogService,
+ @IFileService private readonly fileService: IFileService,
+ @IProductService private readonly productService: IProductService
+ ) {
+ }
+
+ get resourceExtension(): IResourceExtension | undefined {
+ if (this.resourceExtensionInfo) {
+ return this.resourceExtensionInfo.resourceExtension;
+ }
+ if (this.local?.isWorkspaceScoped) {
+ return {
+ type: 'resource',
+ identifier: this.local.identifier,
+ location: this.local.location,
+ manifest: this.local.manifest,
+ changelogUri: this.local.changelogUrl,
+ readmeUri: this.local.readmeUrl,
+ };
+ }
+ return undefined;
+ }
+
+ get gallery(): IGalleryExtension | undefined {
+ return this._gallery;
+ }
+
+ set gallery(gallery: IGalleryExtension | undefined) {
+ this._gallery = gallery;
+ this.galleryResourcesCache.clear();
+ }
+
+ get missingFromGallery(): boolean {
+ return !!this._missingFromGallery;
+ }
+
+ set missingFromGallery(missing: boolean) {
+ this._missingFromGallery = missing;
+ }
+
+ get type(): ExtensionType {
+ return this.local ? this.local.type : ExtensionType.User;
+ }
+
+ get isBuiltin(): boolean {
+ return this.local ? this.local.isBuiltin : false;
+ }
+
+ get isWorkspaceScoped(): boolean {
+ if (this.local) {
+ return this.local.isWorkspaceScoped;
+ }
+ if (this.resourceExtensionInfo) {
+ return this.resourceExtensionInfo.isWorkspaceScoped;
+ }
+ return false;
+ }
+
+ get name(): string {
+ if (this.gallery) {
+ return this.gallery.name;
+ }
+ return this.getManifestFromLocalOrResource()?.name ?? '';
+ }
+
+ get displayName(): string {
+ if (this.gallery) {
+ return this.gallery.displayName || this.gallery.name;
+ }
+
+ return this.getManifestFromLocalOrResource()?.displayName ?? this.name;
+ }
+
+ get identifier(): IExtensionIdentifier {
+ if (this.gallery) {
+ return this.gallery.identifier;
+ }
+ if (this.resourceExtension) {
+ return this.resourceExtension.identifier;
+ }
+ return this.local?.identifier ?? { id: '' };
+ }
+
+ get uuid(): string | undefined {
+ return this.gallery ? this.gallery.identifier.uuid : this.local?.identifier.uuid;
+ }
+
+ get publisher(): string {
+ if (this.gallery) {
+ return this.gallery.publisher;
+ }
+ return this.getManifestFromLocalOrResource()?.publisher ?? '';
+ }
+
+ get publisherDisplayName(): string {
+ if (this.gallery) {
+ return this.gallery.publisherDisplayName || this.gallery.publisher;
+ }
+
+ if (this.local?.publisherDisplayName) {
+ return this.local.publisherDisplayName;
+ }
+
+ return this.publisher;
+ }
+
+ get publisherUrl(): URI | undefined {
+ return this.gallery?.publisherLink ? URI.parse(this.gallery.publisherLink) : undefined;
+ }
+
+ get publisherDomain(): { link: string; verified: boolean } | undefined {
+ return this.gallery?.publisherDomain;
+ }
+
+ get publisherSponsorLink(): URI | undefined {
+ return this.gallery?.publisherSponsorLink ? URI.parse(this.gallery.publisherSponsorLink) : undefined;
+ }
+
+ get version(): string {
+ return this.local ? this.local.manifest.version : this.latestVersion;
+ }
+
+ get private(): boolean {
+ return this.gallery ? this.gallery.private : this.local ? this.local.private : false;
+ }
+
+ get pinned(): boolean {
+ return !!this.local?.pinned;
+ }
+
+ get latestVersion(): string {
+ return this.gallery ? this.gallery.version : this.getManifestFromLocalOrResource()?.version ?? '';
+ }
+
+ get description(): string {
+ return this.gallery ? this.gallery.description : this.getManifestFromLocalOrResource()?.description ?? '';
+ }
+
+ get url(): string | undefined {
+ return this.gallery?.detailsLink;
+ }
+
+ get iconUrl(): string | undefined {
+ return this.galleryIconUrl || this.resourceExtensionIconUrl || this.localIconUrl || this.defaultIconUrl;
+ }
+
+ get iconUrlFallback(): string | undefined {
+ return this.gallery?.assets.icon?.fallbackUri;
+ }
+
+ private get localIconUrl(): string | undefined {
+ if (this.local && this.local.manifest.icon) {
+ return FileAccess.uriToBrowserUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true);
+ }
+ return undefined;
+ }
+
+ private get resourceExtensionIconUrl(): string | undefined {
+ if (this.resourceExtension?.manifest.icon) {
+ return FileAccess.uriToBrowserUri(resources.joinPath(this.resourceExtension.location, this.resourceExtension.manifest.icon)).toString(true);
+ }
+ return undefined;
+ }
+
+ private get galleryIconUrl(): string | undefined {
+ return this.gallery?.assets.icon?.uri;
+ }
+
+ private get defaultIconUrl(): string | undefined {
+ if (this.type === ExtensionType.System && this.local) {
+ if (this.local.manifest && this.local.manifest.contributes) {
+ if (Array.isArray(this.local.manifest.contributes.themes) && this.local.manifest.contributes.themes.length) {
+ return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/theme-icon.png').toString(true);
+ }
+ if (Array.isArray(this.local.manifest.contributes.grammars) && this.local.manifest.contributes.grammars.length) {
+ return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/language-icon.svg').toString(true);
+ }
+ }
+ }
+ return undefined;
+ }
+
+ get repository(): string | undefined {
+ return this.gallery && this.gallery.assets.repository ? this.gallery.assets.repository.uri : undefined;
+ }
+
+ get licenseUrl(): string | undefined {
+ return this.gallery && this.gallery.assets.license ? this.gallery.assets.license.uri : undefined;
+ }
+
+ get supportUrl(): string | undefined {
+ return this.gallery && this.gallery.supportLink ? this.gallery.supportLink : undefined;
+ }
+
+ get state(): ExtensionState {
+ return this.stateProvider(this);
+ }
+
+ private malicious: MaliciousExtensionInfo | undefined;
+ public get isMalicious(): boolean | undefined {
+ return !!this.malicious || this.enablementState === EnablementState.DisabledByMalicious;
+ }
+
+ public get maliciousInfoLink(): string | undefined {
+ return this.malicious?.learnMoreLink;
+ }
+
+ public deprecationInfo: IDeprecationInfo | undefined;
+
+ get installCount(): number | undefined {
+ return this.gallery ? this.gallery.installCount : undefined;
+ }
+
+ get rating(): number | undefined {
+ return this.gallery ? this.gallery.rating : undefined;
+ }
+
+ get ratingCount(): number | undefined {
+ return this.gallery ? this.gallery.ratingCount : undefined;
+ }
+
+ get ratingUrl(): string | undefined {
+ return this.gallery?.ratingLink;
+ }
+
+ get outdated(): boolean {
+ try {
+ if (!this.gallery || !this.local) {
+ return false;
+ }
+ // Do not allow updating system extensions in stable
+ if (this.type === ExtensionType.System && this.productService.quality === 'stable') {
+ return false;
+ }
+ if (!this.local.preRelease && this.gallery.properties.isPreReleaseVersion) {
+ return false;
+ }
+ if (semver.gt(this.latestVersion, this.version)) {
+ return true;
+ }
+ if (this.outdatedTargetPlatform) {
+ return true;
+ }
+ } catch (error) {
+ /* Ignore */
+ }
+ return false;
+ }
+
+ get outdatedTargetPlatform(): boolean {
+ return !!this.local && !!this.gallery
+ && ![TargetPlatform.UNDEFINED, TargetPlatform.WEB].includes(this.local.targetPlatform)
+ && this.gallery.properties.targetPlatform !== TargetPlatform.WEB
+ && this.local.targetPlatform !== this.gallery.properties.targetPlatform
+ && semver.eq(this.latestVersion, this.version);
+ }
+
+ get runtimeState(): ExtensionRuntimeState | undefined {
+ return this.runtimeStateProvider(this);
+ }
+
+ get telemetryData(): any {
+ const { local, gallery } = this;
+
+ if (gallery) {
+ return getGalleryExtensionTelemetryData(gallery);
+ } else if (local) {
+ return getLocalExtensionTelemetryData(local);
+ } else {
+ return {};
+ }
+ }
+
+ get preview(): boolean {
+ return this.local?.manifest.preview ?? this.gallery?.preview ?? false;
+ }
+
+ get preRelease(): boolean {
+ return !!this.local?.preRelease;
+ }
+
+ get isPreReleaseVersion(): boolean {
+ if (this.local) {
+ return this.local.isPreReleaseVersion;
+ }
+ return !!this.gallery?.properties.isPreReleaseVersion;
+ }
+
+ get hasPreReleaseVersion(): boolean {
+ return this.gallery ? this.gallery.hasPreReleaseVersion : !!this.local?.hasPreReleaseVersion;
+ }
+
+ get hasReleaseVersion(): boolean {
+ return !!this.resourceExtension || !!this.gallery?.hasReleaseVersion;
+ }
+
+ private getLocal(): ILocalExtension | undefined {
+ return this.local && !this.outdated ? this.local : undefined;
+ }
+
+ async getManifest(token: CancellationToken): Promise<IExtensionManifest | null> {
+ const local = this.getLocal();
+ if (local) {
+ return local.manifest;
+ }
+
+ if (this.gallery) {
+ return this.getGalleryManifest(token);
+ }
+
+ if (this.resourceExtension) {
+ return this.resourceExtension.manifest;
+ }
+
+ return null;
+ }
+
+ async getGalleryManifest(token: CancellationToken = CancellationToken.None): Promise<IExtensionManifest | null> {
+ if (this.gallery) {
+ let cache = this.galleryResourcesCache.get('manifest');
+ if (!cache) {
+ if (this.gallery.assets.manifest) {
+ this.galleryResourcesCache.set('manifest', cache = this.galleryService.getManifest(this.gallery, token)
+ .catch(e => {
+ this.galleryResourcesCache.delete('manifest');
+ throw e;
+ }));
+ } else {
+ this.logService.error(nls.localize('Manifest is not found', "Manifest is not found"), this.identifier.id);
+ }
+ }
+ return cache;
+ }
+ return null;
+ }
+
+ hasReadme(): boolean {
+ if (this.local && this.local.readmeUrl) {
+ return true;
+ }
+
+ if (this.gallery && this.gallery.assets.readme) {
+ return true;
+ }
+
+ if (this.resourceExtension?.readmeUri) {
+ return true;
+ }
+
+ return this.type === ExtensionType.System;
+ }
+
+ async getReadme(token: CancellationToken): Promise<string> {
+ const local = this.getLocal();
+ if (local?.readmeUrl) {
+ const content = await this.fileService.readFile(local.readmeUrl);
+ return content.value.toString();
+ }
+
+ if (this.gallery) {
+ if (this.gallery.assets.readme) {
+ return this.galleryService.getReadme(this.gallery, token);
+ }
+ this.telemetryService.publicLog('extensions:NotFoundReadMe', this.telemetryData);
+ }
+
+ if (this.type === ExtensionType.System) {
+ return Promise.resolve(`# ${this.displayName || this.name}
+**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
+## Features
+${this.description}
+`);
+ }
+
+ if (this.resourceExtension?.readmeUri) {
+ const content = await this.fileService.readFile(this.resourceExtension?.readmeUri);
+ return content.value.toString();
+ }
+
+ return Promise.reject(new Error('not available'));
+ }
+
+ hasChangelog(): boolean {
+ if (this.local && this.local.changelogUrl) {
+ return true;
+ }
+
+ if (this.gallery && this.gallery.assets.changelog) {
+ return true;
+ }
+
+ return this.type === ExtensionType.System;
+ }
+
+ async getChangelog(token: CancellationToken): Promise<string> {
+ const local = this.getLocal();
+ if (local?.changelogUrl) {
+ const content = await this.fileService.readFile(local.changelogUrl);
+ return content.value.toString();
+ }
+
+ if (this.gallery?.assets.changelog) {
+ return this.galleryService.getChangelog(this.gallery, token);
+ }
+
+ if (this.type === ExtensionType.System) {
+ return Promise.resolve(`Please check the [VS Code Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`);
+ }
+
+ return Promise.reject(new Error('not available'));
+ }
+
+ get categories(): readonly string[] {
+ const { local, gallery, resourceExtension } = this;
+ if (local && local.manifest.categories && !this.outdated) {
+ return local.manifest.categories;
+ }
+ if (gallery) {
+ return gallery.categories;
+ }
+ if (resourceExtension) {
+ return resourceExtension.manifest.categories ?? [];
+ }
+ return [];
+ }
+
+ get tags(): readonly string[] {
+ const { gallery } = this;
+ if (gallery) {
+ return gallery.tags.filter(tag => !tag.startsWith('_'));
+ }
+ return [];
+ }
+
+ get dependencies(): string[] {
+ const { local, gallery, resourceExtension } = this;
+ if (local && local.manifest.extensionDependencies && !this.outdated) {
+ return local.manifest.extensionDependencies;
+ }
+ if (gallery) {
+ return gallery.properties.dependencies || [];
+ }
+ if (resourceExtension) {
+ return resourceExtension.manifest.extensionDependencies || [];
+ }
+ return [];
+ }
+
+ get extensionPack(): string[] {
+ const { local, gallery, resourceExtension } = this;
+ if (local && local.manifest.extensionPack && !this.outdated) {
+ return local.manifest.extensionPack;
+ }
+ if (gallery) {
+ return gallery.properties.extensionPack || [];
+ }
+ if (resourceExtension) {
+ return resourceExtension.manifest.extensionPack || [];
+ }
+ return [];
+ }
+
+ setExtensionsControlManifest(extensionsControlManifest: IExtensionsControlManifest): void {
+ this.malicious = findMatchingMaliciousEntry(this.identifier, extensionsControlManifest.malicious);
+ this.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[this.identifier.id.toLowerCase()] : undefined;
+ }
+
+ private getManifestFromLocalOrResource(): IExtensionManifest | null {
+ if (this.local) {
+ return this.local.manifest;
+ }
+ if (this.resourceExtension) {
+ return this.resourceExtension.manifest;
+ }
+ return null;
+ }
+}
diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.ts b/src/vs/workbench/contrib/extensions/browser/extensions.ts
new file mode 100644
index 00000000000..65ac233b69b
--- /dev/null
+++ b/src/vs/workbench/contrib/extensions/browser/extensions.ts
@@ -0,0 +1,417 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IMarkdownString } from '../../../../base/common/htmlContent.js';
+import { Event, Emitter } from '../../../../base/common/event.js';
+import { index } from '../../../../base/common/arrays.js';
+import { CancellationToken } from '../../../../base/common/cancellation.js';
+import { Disposable } from '../../../../base/common/lifecycle.js';
+import { URI } from '../../../../base/common/uri.js';
+import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
+import { TelemetryTrustedValue } from '../../../../platform/telemetry/common/telemetryUtils.js';
+import {
+ IExtensionGalleryService, IGalleryExtension,
+ InstallExtensionEvent, InstallOperation, InstallExtensionResult,
+ IExtensionInfo, IProductVersion, ILocalExtension, DidUninstallExtensionEvent
+} from '../../../../platform/extensionManagement/common/extensionManagement.js';
+import { IWorkbenchExtensionEnablementService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js';
+import { areSameExtensions, groupByExtension } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
+import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
+import { IExtension, ExtensionState, ExtensionRuntimeState } from '../common/extensions.js';
+import { IExtensionIdentifier, IExtension as IPlatformExtension, ExtensionType } from '../../../../platform/extensions/common/extensions.js';
+import { Extension, IExtensionStateProvider } from './extension.js';
+
+export interface InstalledExtensionsEvent {
+ readonly extensionIds: TelemetryTrustedValue<string>;
+ readonly count: number;
+}
+
+export type ExtensionsLoadClassification = {
+ owner: 'digitarald';
+ comment: 'Helps to understand which extensions are the most actively used.';
+ readonly extensionIds: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The list of extension ids that are installed.' };
+ readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The number of extensions that are installed.' };
+};
+
+export class Extensions extends Disposable {
+
+ private readonly _onChange = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>());
+ get onChange() { return this._onChange.event; }
+
+ private readonly _onReset = this._register(new Emitter<void>());
+ get onReset() { return this._onReset.event; }
+
+ private installing: Extension[] = [];
+ private uninstalling: Extension[] = [];
+ private installed: Extension[] = [];
+
+ constructor(
+ readonly server: IExtensionManagementServer,
+ private readonly stateProvider: IExtensionStateProvider<ExtensionState>,
+ private readonly runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,
+ private readonly isWorkspaceServer: boolean,
+ @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
+ @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
+ @IWorkbenchExtensionManagementService private readonly workbenchExtensionManagementService: IWorkbenchExtensionManagementService,
+ @ITelemetryService private readonly telemetryService: ITelemetryService,
+ @IInstantiationService private readonly instantiationService: IInstantiationService
+ ) {
+ super();
+ this._register(server.extensionManagementService.onInstallExtension(e => this.onInstallExtension(e)));
+ this._register(server.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
+ this._register(server.extensionManagementService.onUninstallExtension(e => this.onUninstallExtension(e.identifier)));
+ this._register(server.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
+ this._register(server.extensionManagementService.onDidUpdateExtensionMetadata(e => this.onDidUpdateExtensionMetadata(e.local)));
+ this._register(server.extensionManagementService.onDidChangeProfile(() => this.reset()));
+ this._register(extensionEnablementService.onEnablementChanged(e => this.onEnablementChanged(e)));
+ this._register(Event.any(this.onChange, this.onReset)(() => this._local = undefined));
+ if (this.isWorkspaceServer) {
+ this._register(this.workbenchExtensionManagementService.onInstallExtension(e => {
+ if (e.workspaceScoped) {
+ this.onInstallExtension(e);
+ }
+ }));
+ this._register(this.workbenchExtensionManagementService.onDidInstallExtensions(e => {
+ const result = e.filter(e => e.workspaceScoped);
+ if (result.length) {
+ this.onDidInstallExtensions(result);
+ }
+ }));
+ this._register(this.workbenchExtensionManagementService.onUninstallExtension(e => {
+ if (e.workspaceScoped) {
+ this.onUninstallExtension(e.identifier);
+ }
+ }));
+ this._register(this.workbenchExtensionManagementService.onDidUninstallExtension(e => {
+ if (e.workspaceScoped) {
+ this.onDidUninstallExtension(e);
+ }
+ }));
+ }
+ }
+
+ private _local: Extension[] | undefined;
+ get local(): Extension[] {
+ if (!this._local) {
+ this._local = [];
+ for (const extension of this.installed) {
+ this._local.push(extension);
+ }
+ for (const extension of this.installing) {
+ if (!this.installed.some(installed => areSameExtensions(installed.identifier, extension.identifier))) {
+ this._local.push(extension);
+ }
+ }
+ }
+ return this._local;
+ }
+
+ async queryInstalled(productVersion: IProductVersion): Promise<IExtension[]> {
+ await this.fetchInstalledExtensions(productVersion);
+ this._onChange.fire(undefined);
+ return this.local;
+ }
+
+ async syncInstalledExtensionsWithGallery(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion, flagExtensionsMissingFromGallery?: IExtensionInfo[]): Promise<void> {
+ const extensions = await this.mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions, productVersion);
+ for (const [extension, gallery] of extensions) {
+ // update metadata of the extension if it does not exist
+ if (extension.local && !extension.local.identifier.uuid) {
+ extension.local = await this.updateMetadata(extension.local, gallery);
+ }
+ if (!extension.gallery || extension.gallery.version !== gallery.version || extension.gallery.properties.targetPlatform !== gallery.properties.targetPlatform) {
+ extension.gallery = gallery;
+ this._onChange.fire({ extension });
+ }
+ }
+ // Detect extensions that do not have a corresponding gallery entry.
+ if (flagExtensionsMissingFromGallery) {
+ const extensionsToQuery = [];
+ for (const extension of this.local) {
+ // Extension is already paired with a gallery object
+ if (extension.gallery) {
+ continue;
+ }
+ // Already flagged as missing from gallery
+ if (extension.missingFromGallery) {
+ continue;
+ }
+ // A UUID indicates extension originated from gallery
+ if (!extension.identifier.uuid) {
+ continue;
+ }
+ // Extension is not present in the set we are concerned about
+ if (!flagExtensionsMissingFromGallery.some(f => areSameExtensions(f, extension.identifier))) {
+ continue;
+ }
+ extensionsToQuery.push(extension);
+ }
+ if (extensionsToQuery.length) {
+ const queryResult = await this.galleryService.getExtensions(extensionsToQuery.map(e => ({ ...e.identifier, version: e.version })), CancellationToken.None);
+ const queriedIds: string[] = [];
+ const missingIds: string[] = [];
+ for (const extension of extensionsToQuery) {
+ queriedIds.push(extension.identifier.id);
+ const gallery = queryResult.find(g => areSameExtensions(g.identifier, extension.identifier));
+ if (gallery) {
+ extension.gallery = gallery;
+ } else {
+ extension.missingFromGallery = true;
+ missingIds.push(extension.identifier.id);
+ }
+ this._onChange.fire({ extension });
+ }
+ type MissingFromGalleryClassification = {
+ owner: 'joshspicer';
+ comment: 'Report when installed extensions are no longer available in the gallery';
+ queriedIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions queried as potentially missing from gallery' };
+ missingIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions determined missing from gallery' };
+ };
+ type MissingFromGalleryEvent = {
+ readonly queriedIds: TelemetryTrustedValue<string>;
+ readonly missingIds: TelemetryTrustedValue<string>;
+ };
+ this.telemetryService.publicLog2<MissingFromGalleryEvent, MissingFromGalleryClassification>('extensions:missingFromGallery', {
+ queriedIds: new TelemetryTrustedValue(queriedIds.join(';')),
+ missingIds: new TelemetryTrustedValue(missingIds.join(';'))
+ });
+ }
+ }
+ }
+
+ private async mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise<[Extension, IGalleryExtension][]> {
+ const mappedExtensions = this.mapInstalledExtensionWithGalleryExtension(galleryExtensions);
+ const targetPlatform = await this.server.extensionManagementService.getTargetPlatform();
+ const compatibleGalleryExtensions: IGalleryExtension[] = [];
+ const compatibleGalleryExtensionsToFetch: IExtensionInfo[] = [];
+ await Promise.allSettled(mappedExtensions.map(async ([extension, gallery]) => {
+ if (extension.local) {
+ if (await this.galleryService.isExtensionCompatible(gallery, extension.local.preRelease, targetPlatform, productVersion)) {
+ compatibleGalleryExtensions.push(gallery);
+ } else {
+ compatibleGalleryExtensionsToFetch.push({ ...extension.local.identifier, preRelease: extension.local.preRelease });
+ }
+ }
+ }));
+ if (compatibleGalleryExtensionsToFetch.length) {
+ const result = await this.galleryService.getExtensions(compatibleGalleryExtensionsToFetch, { targetPlatform, compatible: true, queryAllVersions: true, productVersion }, CancellationToken.None);
+ compatibleGalleryExtensions.push(...result);
+ }
+ return this.mapInstalledExtensionWithGalleryExtension(compatibleGalleryExtensions);
+ }
+
+ private mapInstalledExtensionWithGalleryExtension(galleryExtensions: IGalleryExtension[]): [Extension, IGalleryExtension][] {
+ const mappedExtensions: [Extension, IGalleryExtension][] = [];
+ const byUUID = new Map<string, IGalleryExtension>(), byID = new Map<string, IGalleryExtension>();
+ for (const gallery of galleryExtensions) {
+ byUUID.set(gallery.identifier.uuid, gallery);
+ byID.set(gallery.identifier.id.toLowerCase(), gallery);
+ }
+ for (const installed of this.installed) {
+ if (installed.uuid) {
+ const gallery = byUUID.get(installed.uuid);
+ if (gallery) {
+ mappedExtensions.push([installed, gallery]);
+ continue;
+ }
+ }
+ if (installed.local?.source !== 'resource') {
+ const gallery = byID.get(installed.identifier.id.toLowerCase());
+ if (gallery) {
+ mappedExtensions.push([installed, gallery]);
+ }
+ }
+ }
+ return mappedExtensions;
+ }
+
+ private async updateMetadata(localExtension: ILocalExtension, gallery: IGalleryExtension): Promise<ILocalExtension> {
+ let isPreReleaseVersion = false;
+ if (localExtension.manifest.version !== gallery.version) {
+ type GalleryServiceMatchInstalledExtensionClassification = {
+ owner: 'sandy081';
+ comment: 'Report when a request is made to update metadata of an installed extension';
+ };
+ this.telemetryService.publicLog2<{}, GalleryServiceMatchInstalledExtensionClassification>('galleryService:updateMetadata');
+ const galleryWithLocalVersion: IGalleryExtension | undefined = (await this.galleryService.getExtensions([{ ...localExtension.identifier, version: localExtension.manifest.version }], CancellationToken.None))[0];
+ isPreReleaseVersion = !!galleryWithLocalVersion?.properties?.isPreReleaseVersion;
+ }
+ return this.workbenchExtensionManagementService.updateMetadata(localExtension, { id: gallery.identifier.uuid, publisherDisplayName: gallery.publisherDisplayName, publisherId: gallery.publisherId, isPreReleaseVersion });
+ }
+
+ canInstall(galleryExtension: IGalleryExtension): Promise<true | IMarkdownString> {
+ return this.server.extensionManagementService.canInstall(galleryExtension);
+ }
+
+ private onInstallExtension(event: InstallExtensionEvent): void {
+ const { source } = event;
+ if (source && !URI.isUri(source)) {
+ const extension = this.installed.find(e => areSameExtensions(e.identifier, source.identifier))
+ ?? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, undefined, source, undefined);
+ this.installing.push(extension);
+ this._onChange.fire({ extension });
+ }
+ }
+
+ private async fetchInstalledExtensions(productVersion?: IProductVersion): Promise<void> {
+ const extensionsControlManifest = await this.server.extensionManagementService.getExtensionsControlManifest();
+ const all = await this.server.extensionManagementService.getInstalled(undefined, undefined, productVersion);
+ if (this.isWorkspaceServer) {
+ all.push(...await this.workbenchExtensionManagementService.getInstalledWorkspaceExtensions(true));
+ }
+
+ // dedup workspace, user and system extensions by giving priority to workspace first and then to user extension.
+ const installed = groupByExtension(all, r => r.identifier).reduce((result, extensions) => {
+ if (extensions.length === 1) {
+ result.push(extensions[0]);
+ } else {
+ let workspaceExtension: IPlatformExtension | undefined,
+ userExtension: IPlatformExtension | undefined,
+ systemExtension: IPlatformExtension | undefined;
+ for (const extension of extensions) {
+ if (extension.isWorkspaceScoped) {
+ workspaceExtension = extension;
+ } else if (extension.type === ExtensionType.User) {
+ userExtension = extension;
+ } else {
+ systemExtension = extension;
+ }
+ }
+ const extension = workspaceExtension ?? userExtension ?? systemExtension;
+ if (extension) {
+ result.push(extension);
+ }
+ }
+ return result;
+ }, [] as IPlatformExtension[]);
+
+ const byId = index(this.installed, e => e.local ? e.local.identifier.id : e.identifier.id);
+ this.installed = installed.map(local => {
+ const extension = byId[local.identifier.id] || this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local as ILocalExtension, undefined, undefined);
+ extension.local = local as ILocalExtension;
+ extension.enablementState = this.extensionEnablementService.getEnablementState(local as ILocalExtension);
+ extension.setExtensionsControlManifest(extensionsControlManifest);
+ return extension;
+ });
+ }
+
+ private async reset(): Promise<void> {
+ this.installed = [];
+ this.installing = [];
+ this.uninstalling = [];
+ await this.fetchInstalledExtensions();
+ this._onReset.fire();
+ }
+
+ private async onDidInstallExtensions(results: readonly InstallExtensionResult[]): Promise<void> {
+ const extensions: Extension[] = [];
+ for (const event of results) {
+ const { local, source } = event;
+ const gallery = source && !URI.isUri(source) ? source : undefined;
+ const location = source && URI.isUri(source) ? source : undefined;
+ const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] : null;
+ this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing;
+
+ let extension: Extension | undefined = installingExtension ? installingExtension
+ : (location || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined)
+ : undefined;
+ if (extension) {
+ if (local) {
+ const installed = this.installed.filter(e => areSameExtensions(e.identifier, extension!.identifier))[0];
+ if (installed) {
+ extension = installed;
+ } else {
+ this.installed.push(extension);
+ }
+ extension.local = local;
+ if (!extension.gallery) {
+ extension.gallery = gallery;
+ }
+ extension.enablementState = this.extensionEnablementService.getEnablementState(local);
+ }
+ extensions.push(extension);
+ }
+ this._onChange.fire(!local || !extension ? undefined : { extension, operation: event.operation });
+ }
+
+ if (extensions.length) {
+ const manifest = await this.server.extensionManagementService.getExtensionsControlManifest();
+ for (const extension of extensions) {
+ extension.setExtensionsControlManifest(manifest);
+ }
+ this.matchInstalledExtensionsWithGallery(extensions);
+ }
+ }
+
+ private async onDidUpdateExtensionMetadata(local: ILocalExtension): Promise<void> {
+ const extension = this.installed.find(e => areSameExtensions(e.identifier, local.identifier));
+ if (extension?.local) {
+ extension.local = local;
+ this._onChange.fire({ extension });
+ }
+ }
+
+ private async matchInstalledExtensionsWithGallery(extensions: Extension[]): Promise<void> {
+ const toMatch = extensions.filter(e => e.local && !e.gallery && e.local.source !== 'resource');
+ if (!toMatch.length) {
+ return;
+ }
+ if (!this.galleryService.isEnabled()) {
+ return;
+ }
+ const galleryExtensions = await this.galleryService.getExtensions(toMatch.map(e => ({ ...e.identifier, preRelease: e.local?.preRelease })), { compatible: true, targetPlatform: await this.server.extensionManagementService.getTargetPlatform() }, CancellationToken.None);
+ for (const extension of extensions) {
+ const compatible = galleryExtensions.find(e => areSameExtensions(e.identifier, extension.identifier));
+ if (compatible) {
+ extension.gallery = compatible;
+ this._onChange.fire({ extension });
+ }
+ }
+ }
+
+ private onUninstallExtension(identifier: IExtensionIdentifier): void {
+ const extension = this.installed.filter(e => areSameExtensions(e.identifier, identifier))[0];
+ if (extension) {
+ const uninstalling = this.uninstalling.filter(e => areSameExtensions(e.identifier, identifier))[0] || extension;
+ this.uninstalling = [uninstalling, ...this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier))];
+ this._onChange.fire(uninstalling ? { extension: uninstalling } : undefined);
+ }
+ }
+
+ private onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
+ const uninstalled = this.uninstalling.find(e => areSameExtensions(e.identifier, identifier)) || this.installed.find(e => areSameExtensions(e.identifier, identifier));
+ this.uninstalling = this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier));
+ if (!error) {
+ this.installed = this.installed.filter(e => !areSameExtensions(e.identifier, identifier));
+ }
+ if (uninstalled) {
+ this._onChange.fire({ extension: uninstalled });
+ }
+ }
+
+ private onEnablementChanged(platformExtensions: readonly IPlatformExtension[]) {
+ const extensions = this.local.filter(e => platformExtensions.some(p => areSameExtensions(e.identifier, p.identifier)));
+ for (const extension of extensions) {
+ if (extension.local) {
+ const enablementState = this.extensionEnablementService.getEnablementState(extension.local);
+ if (enablementState !== extension.enablementState) {
+ extension.enablementState = enablementState;
+ this._onChange.fire({ extension });
+ }
+ }
+ }
+ }
+
+ getExtensionState(extension: Extension): ExtensionState {
+ if (extension.gallery && this.installing.some(e => !!e.gallery && areSameExtensions(e.gallery.identifier, extension.gallery!.identifier))) {
+ return ExtensionState.Installing;
+ }
+ if (this.uninstalling.some(e => areSameExtensions(e.identifier, extension.identifier))) {
+ return ExtensionState.Uninstalling;
+ }
+ const local = this.installed.filter(e => e === extension || (e.gallery && extension.gallery && areSameExtensions(e.gallery.identifier, extension.gallery.identifier)))[0];
+ return local ? ExtensionState.Installed : ExtensionState.Uninstalled;
+ }
+}
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
index de710641876..6d079d6fc50 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -6,7 +6,6 @@
import * as nls from '../../../../nls.js';
import * as semver from '../../../../base/common/semver/semver.js';
import { Event, Emitter } from '../../../../base/common/event.js';
-import { index } from '../../../../base/common/arrays.js';
import { CancelablePromise, Promises, ThrottledDelayer, createCancelablePromise } from '../../../../base/common/async.js';
import { CancellationError, getErrorMessage, isCancellationError } from '../../../../base/common/errors.js';
import { Disposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
@@ -14,8 +13,8 @@ import { IPager, singlePagePager } from '../../../../base/common/paging.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import {
IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
- InstallExtensionEvent, DidUninstallExtensionEvent, InstallOperation, WEB_EXTENSION_TAG, InstallExtensionResult,
- IExtensionsControlManifest, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible, InstallExtensionInfo, EXTENSION_IDENTIFIER_REGEX,
+ InstallOperation, WEB_EXTENSION_TAG, InstallExtensionResult,
+ IExtensionsControlManifest, IExtensionInfo, IExtensionQueryOptions, isTargetPlatformCompatible, InstallExtensionInfo, EXTENSION_IDENTIFIER_REGEX,
InstallOptions, IProductVersion,
UninstallExtensionInfo,
TargetPlatformToString,
@@ -24,31 +23,28 @@ import {
EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT,
ExtensionManagementError,
ExtensionManagementErrorCode,
- MaliciousExtensionInfo,
shouldRequireRepositorySignatureFor,
IGalleryExtensionVersion
} from '../../../../platform/extensionManagement/common/extensionManagement.js';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, IResourceExtension } from '../../../services/extensionManagement/common/extensionManagement.js';
-import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, getGalleryExtensionId, findMatchingMaliciousEntry } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
+import { areSameExtensions, groupByExtension, getGalleryExtensionId } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { IHostService } from '../../../services/host/browser/host.js';
import { URI } from '../../../../base/common/uri.js';
-import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions, ExtensionRuntimeState, ExtensionRuntimeActionType, AutoRestartConfigurationKey, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionsNotification } from '../common/extensions.js';
+import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoUpdateConfigurationValue, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, InstallExtensionOptions, ExtensionRuntimeState, ExtensionRuntimeActionType, AutoRestartConfigurationKey, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionsNotification } from '../common/extensions.js';
import { ACTIVE_GROUP, IEditorService, MODAL_GROUP, SIDE_GROUP } from '../../../services/editor/common/editorService.js';
import { IURLService, IURLHandler, IOpenURLOptions } from '../../../../platform/url/common/url.js';
import { ExtensionsInput, IExtensionEditorOptions } from '../common/extensionsInput.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { IProgressOptions, IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';
import { INotificationService, NotificationPriority, Severity } from '../../../../platform/notification/common/notification.js';
-import * as resources from '../../../../base/common/resources.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
import { IFileService } from '../../../../platform/files/common/files.js';
-import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, TargetPlatform, ExtensionIdentifier, IExtensionIdentifier, IExtensionDescription, isApplicationScopedExtension } from '../../../../platform/extensions/common/extensions.js';
+import { ExtensionType, ExtensionIdentifier, IExtensionIdentifier, IExtensionDescription, isApplicationScopedExtension, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';
import { ILanguageService } from '../../../../editor/common/languages/language.js';
import { IProductService } from '../../../../platform/product/common/productService.js';
-import { FileAccess } from '../../../../base/common/network.js';
import { IIgnoredExtensionsManagementService } from '../../../../platform/userDataSync/common/ignoredExtensions.js';
import { IUserDataAutoSyncService, IUserDataSyncEnablementService, SyncResource } from '../../../../platform/userDataSync/common/userDataSync.js';
import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
@@ -67,7 +63,6 @@ import { IUpdateService, StateType } from '../../../../platform/update/common/up
import { areApiProposalsCompatible, isEngineValid } from '../../../../platform/extensions/common/extensionValidator.js';
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
-import { ShowCurrentReleaseNotesActionId } from '../../update/common/update.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';
import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';
@@ -75,902 +70,16 @@ import { ExtensionGalleryResourceType, getExtensionGalleryManifestResourceUri, I
import { fromNow } from '../../../../base/common/date.js';
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
import { IMeteredConnectionService } from '../../../../platform/meteredConnection/common/meteredConnection.js';
+import { Extension } from './extension.js';
+import { Extensions, InstalledExtensionsEvent, ExtensionsLoadClassification } from './extensions.js';
-interface IExtensionStateProvider<T> {
- (extension: Extension): T;
-}
-
-interface InstalledExtensionsEvent {
- readonly extensionIds: TelemetryTrustedValue<string>;
- readonly count: number;
-}
-type ExtensionsLoadClassification = {
- owner: 'digitarald';
- comment: 'Helps to understand which extensions are the most actively used.';
- readonly extensionIds: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The list of extension ids that are installed.' };
- readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The number of extensions that are installed.' };
-};
-
-export class Extension implements IExtension {
-
- public enablementState: EnablementState = EnablementState.EnabledGlobally;
-
- private galleryResourcesCache = new Map<string, any>();
-
- private _missingFromGallery: boolean | undefined;
+export { Extension };
- constructor(
- private stateProvider: IExtensionStateProvider<ExtensionState>,
- private runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,
- public readonly server: IExtensionManagementServer | undefined,
- public local: ILocalExtension | undefined,
- private _gallery: IGalleryExtension | undefined,
- private readonly resourceExtensionInfo: { resourceExtension: IResourceExtension; isWorkspaceScoped: boolean } | undefined,
- @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
- @ITelemetryService private readonly telemetryService: ITelemetryService,
- @ILogService private readonly logService: ILogService,
- @IFileService private readonly fileService: IFileService,
- @IProductService private readonly productService: IProductService
- ) {
- }
-
- get resourceExtension(): IResourceExtension | undefined {
- if (this.resourceExtensionInfo) {
- return this.resourceExtensionInfo.resourceExtension;
- }
- if (this.local?.isWorkspaceScoped) {
- return {
- type: 'resource',
- identifier: this.local.identifier,
- location: this.local.location,
- manifest: this.local.manifest,
- changelogUri: this.local.changelogUrl,
- readmeUri: this.local.readmeUrl,
- };
- }
- return undefined;
- }
-
- get gallery(): IGalleryExtension | undefined {
- return this._gallery;
- }
-
- set gallery(gallery: IGalleryExtension | undefined) {
- this._gallery = gallery;
- this.galleryResourcesCache.clear();
- }
-
- get missingFromGallery(): boolean {
- return !!this._missingFromGallery;
- }
-
- set missingFromGallery(missing: boolean) {
- this._missingFromGallery = missing;
- }
-
- get type(): ExtensionType {
- return this.local ? this.local.type : ExtensionType.User;
- }
-
- get isBuiltin(): boolean {
- return this.local ? this.local.isBuiltin : false;
- }
-
- get isWorkspaceScoped(): boolean {
- if (this.local) {
- return this.local.isWorkspaceScoped;
- }
- if (this.resourceExtensionInfo) {
- return this.resourceExtensionInfo.isWorkspaceScoped;
- }
- return false;
- }
-
- get name(): string {
- if (this.gallery) {
- return this.gallery.name;
- }
- return this.getManifestFromLocalOrResource()?.name ?? '';
- }
-
- get displayName(): string {
- if (this.gallery) {
- return this.gallery.displayName || this.gallery.name;
- }
-
- return this.getManifestFromLocalOrResource()?.displayName ?? this.name;
- }
-
- get identifier(): IExtensionIdentifier {
- if (this.gallery) {
- return this.gallery.identifier;
- }
- if (this.resourceExtension) {
- return this.resourceExtension.identifier;
- }
- return this.local?.identifier ?? { id: '' };
- }
-
- get uuid(): string | undefined {
- return this.gallery ? this.gallery.identifier.uuid : this.local?.identifier.uuid;
- }
-
- get publisher(): string {
- if (this.gallery) {
- return this.gallery.publisher;
- }
- return this.getManifestFromLocalOrResource()?.publisher ?? '';
- }
-
- get publisherDisplayName(): string {
- if (this.gallery) {
- return this.gallery.publisherDisplayName || this.gallery.publisher;
- }
-
- if (this.local?.publisherDisplayName) {
- return this.local.publisherDisplayName;
- }
-
- return this.publisher;
- }
-
- get publisherUrl(): URI | undefined {
- return this.gallery?.publisherLink ? URI.parse(this.gallery.publisherLink) : undefined;
- }
-
- get publisherDomain(): { link: string; verified: boolean } | undefined {
- return this.gallery?.publisherDomain;
- }
-
- get publisherSponsorLink(): URI | undefined {
- return this.gallery?.publisherSponsorLink ? URI.parse(this.gallery.publisherSponsorLink) : undefined;
- }
-
- get version(): string {
- return this.local ? this.local.manifest.version : this.latestVersion;
- }
-
- get private(): boolean {
- return this.gallery ? this.gallery.private : this.local ? this.local.private : false;
- }
-
- get pinned(): boolean {
- return !!this.local?.pinned;
- }
-
- get latestVersion(): string {
- return this.gallery ? this.gallery.version : this.getManifestFromLocalOrResource()?.version ?? '';
- }
-
- get description(): string {
- return this.gallery ? this.gallery.description : this.getManifestFromLocalOrResource()?.description ?? '';
- }
-
- get url(): string | undefined {
- return this.gallery?.detailsLink;
- }
-
- get iconUrl(): string | undefined {
- return this.galleryIconUrl || this.resourceExtensionIconUrl || this.localIconUrl || this.defaultIconUrl;
- }
-
- get iconUrlFallback(): string | undefined {
- return this.gallery?.assets.icon?.fallbackUri;
- }
-
- private get localIconUrl(): string | undefined {
- if (this.local && this.local.manifest.icon) {
- return FileAccess.uriToBrowserUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true);
- }
- return undefined;
- }
-
- private get resourceExtensionIconUrl(): string | undefined {
- if (this.resourceExtension?.manifest.icon) {
- return FileAccess.uriToBrowserUri(resources.joinPath(this.resourceExtension.location, this.resourceExtension.manifest.icon)).toString(true);
- }
- return undefined;
- }
-
- private get galleryIconUrl(): string | undefined {
- return this.gallery?.assets.icon?.uri;
- }
-
- private get defaultIconUrl(): string | undefined {
- if (this.type === ExtensionType.System && this.local) {
- if (this.local.manifest && this.local.manifest.contributes) {
- if (Array.isArray(this.local.manifest.contributes.themes) && this.local.manifest.contributes.themes.length) {
- return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/theme-icon.png').toString(true);
- }
- if (Array.isArray(this.local.manifest.contributes.grammars) && this.local.manifest.contributes.grammars.length) {
- return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/language-icon.svg').toString(true);
- }
- }
- }
- return undefined;
- }
-
- get repository(): string | undefined {
- return this.gallery && this.gallery.assets.repository ? this.gallery.assets.repository.uri : undefined;
- }
-
- get licenseUrl(): string | undefined {
- return this.gallery && this.gallery.assets.license ? this.gallery.assets.license.uri : undefined;
- }
-
- get supportUrl(): string | undefined {
- return this.gallery && this.gallery.supportLink ? this.gallery.supportLink : undefined;
- }
-
- get state(): ExtensionState {
- return this.stateProvider(this);
- }
-
- private malicious: MaliciousExtensionInfo | undefined;
- public get isMalicious(): boolean | undefined {
- return !!this.malicious || this.enablementState === EnablementState.DisabledByMalicious;
- }
-
- public get maliciousInfoLink(): string | undefined {
- return this.malicious?.learnMoreLink;
- }
-
- public deprecationInfo: IDeprecationInfo | undefined;
-
- get installCount(): number | undefined {
- return this.gallery ? this.gallery.installCount : undefined;
- }
-
- get rating(): number | undefined {
- return this.gallery ? this.gallery.rating : undefined;
- }
-
- get ratingCount(): number | undefined {
- return this.gallery ? this.gallery.ratingCount : undefined;
- }
-
- get ratingUrl(): string | undefined {
- return this.gallery?.ratingLink;
- }
-
- get outdated(): boolean {
- try {
- if (!this.gallery || !this.local) {
- return false;
- }
- // Do not allow updating system extensions in stable
- if (this.type === ExtensionType.System && this.productService.quality === 'stable') {
- return false;
- }
- if (!this.local.preRelease && this.gallery.properties.isPreReleaseVersion) {
- return false;
- }
- if (semver.gt(this.latestVersion, this.version)) {
- return true;
- }
- if (this.outdatedTargetPlatform) {
- return true;
- }
- } catch (error) {
- /* Ignore */
- }
- return false;
- }
-
- get outdatedTargetPlatform(): boolean {
- return !!this.local && !!this.gallery
- && ![TargetPlatform.UNDEFINED, TargetPlatform.WEB].includes(this.local.targetPlatform)
- && this.gallery.properties.targetPlatform !== TargetPlatform.WEB
- && this.local.targetPlatform !== this.gallery.properties.targetPlatform
- && semver.eq(this.latestVersion, this.version);
- }
-
- get runtimeState(): ExtensionRuntimeState | undefined {
- return this.runtimeStateProvider(this);
- }
-
- get telemetryData(): any {
- const { local, gallery } = this;
-
- if (gallery) {
- return getGalleryExtensionTelemetryData(gallery);
- } else if (local) {
- return getLocalExtensionTelemetryData(local);
- } else {
- return {};
- }
- }
-
- get preview(): boolean {
- return this.local?.manifest.preview ?? this.gallery?.preview ?? false;
- }
-
- get preRelease(): boolean {
- return !!this.local?.preRelease;
- }
-
- get isPreReleaseVersion(): boolean {
- if (this.local) {
- return this.local.isPreReleaseVersion;
- }
- return !!this.gallery?.properties.isPreReleaseVersion;
- }
-
- get hasPreReleaseVersion(): boolean {
- return this.gallery ? this.gallery.hasPreReleaseVersion : !!this.local?.hasPreReleaseVersion;
- }
-
- get hasReleaseVersion(): boolean {
- return !!this.resourceExtension || !!this.gallery?.hasReleaseVersion;
- }
-
- private getLocal(): ILocalExtension | undefined {
- return this.local && !this.outdated ? this.local : undefined;
- }
-
- async getManifest(token: CancellationToken): Promise<IExtensionManifest | null> {
- const local = this.getLocal();
- if (local) {
- return local.manifest;
- }
-
- if (this.gallery) {
- return this.getGalleryManifest(token);
- }
-
- if (this.resourceExtension) {
- return this.resourceExtension.manifest;
- }
-
- return null;
- }
-
- async getGalleryManifest(token: CancellationToken = CancellationToken.None): Promise<IExtensionManifest | null> {
- if (this.gallery) {
- let cache = this.galleryResourcesCache.get('manifest');
- if (!cache) {
- if (this.gallery.assets.manifest) {
- this.galleryResourcesCache.set('manifest', cache = this.galleryService.getManifest(this.gallery, token)
- .catch(e => {
- this.galleryResourcesCache.delete('manifest');
- throw e;
- }));
- } else {
- this.logService.error(nls.localize('Manifest is not found', "Manifest is not found"), this.identifier.id);
- }
- }
- return cache;
- }
- return null;
- }
-
- hasReadme(): boolean {
- if (this.local && this.local.readmeUrl) {
- return true;
- }
-
- if (this.gallery && this.gallery.assets.readme) {
- return true;
- }
-
- if (this.resourceExtension?.readmeUri) {
- return true;
- }
-
- return this.type === ExtensionType.System;
- }
-
- async getReadme(token: CancellationToken): Promise<string> {
- const local = this.getLocal();
- if (local?.readmeUrl) {
- const content = await this.fileService.readFile(local.readmeUrl);
- return content.value.toString();
- }
-
- if (this.gallery) {
- if (this.gallery.assets.readme) {
- return this.galleryService.getReadme(this.gallery, token);
- }
- this.telemetryService.publicLog('extensions:NotFoundReadMe', this.telemetryData);
- }
-
- if (this.type === ExtensionType.System) {
- return Promise.resolve(`# ${this.displayName || this.name}
-**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
-## Features
-${this.description}
-`);
- }
-
- if (this.resourceExtension?.readmeUri) {
- const content = await this.fileService.readFile(this.resourceExtension?.readmeUri);
- return content.value.toString();
- }
-
- return Promise.reject(new Error('not available'));
- }
-
- hasChangelog(): boolean {
- if (this.local && this.local.changelogUrl) {
- return true;
- }
-
- if (this.gallery && this.gallery.assets.changelog) {
- return true;
- }
-
- return this.type === ExtensionType.System;
- }
-
- async getChangelog(token: CancellationToken): Promise<string> {
- const local = this.getLocal();
- if (local?.changelogUrl) {
- const content = await this.fileService.readFile(local.changelogUrl);
- return content.value.toString();
- }
-
- if (this.gallery?.assets.changelog) {
- return this.galleryService.getChangelog(this.gallery, token);
- }
-
- if (this.type === ExtensionType.System) {
- return Promise.resolve(`Please check the [VS Code Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`);
- }
-
- return Promise.reject(new Error('not available'));
- }
-
- get categories(): readonly string[] {
- const { local, gallery, resourceExtension } = this;
- if (local && local.manifest.categories && !this.outdated) {
- return local.manifest.categories;
- }
- if (gallery) {
- return gallery.categories;
- }
- if (resourceExtension) {
- return resourceExtension.manifest.categories ?? [];
- }
- return [];
- }
-
- get tags(): readonly string[] {
- const { gallery } = this;
- if (gallery) {
- return gallery.tags.filter(tag => !tag.startsWith('_'));
- }
- return [];
- }
-
- get dependencies(): string[] {
- const { local, gallery, resourceExtension } = this;
- if (local && local.manifest.extensionDependencies && !this.outdated) {
- return local.manifest.extensionDependencies;
- }
- if (gallery) {
- return gallery.properties.dependencies || [];
- }
- if (resourceExtension) {
- return resourceExtension.manifest.extensionDependencies || [];
- }
- return [];
- }
-
- get extensionPack(): string[] {
- const { local, gallery, resourceExtension } = this;
- if (local && local.manifest.extensionPack && !this.outdated) {
- return local.manifest.extensionPack;
- }
- if (gallery) {
- return gallery.properties.extensionPack || [];
- }
- if (resourceExtension) {
- return resourceExtension.manifest.extensionPack || [];
- }
- return [];
- }
-
- setExtensionsControlManifest(extensionsControlManifest: IExtensionsControlManifest): void {
- this.malicious = findMatchingMaliciousEntry(this.identifier, extensionsControlManifest.malicious);
- this.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[this.identifier.id.toLowerCase()] : undefined;
- }
-
- private getManifestFromLocalOrResource(): IExtensionManifest | null {
- if (this.local) {
- return this.local.manifest;
- }
- if (this.resourceExtension) {
- return this.resourceExtension.manifest;
- }
- return null;
- }
-}
const EXTENSIONS_AUTO_UPDATE_KEY = 'extensions.autoUpdate';
const EXTENSIONS_DONOT_AUTO_UPDATE_KEY = 'extensions.donotAutoUpdate';
const EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY = 'extensions.dismissedNotifications';
-class Extensions extends Disposable {
-
- private readonly _onChange = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>());
- get onChange() { return this._onChange.event; }
-
- private readonly _onReset = this._register(new Emitter<void>());
- get onReset() { return this._onReset.event; }
-
- private installing: Extension[] = [];
- private uninstalling: Extension[] = [];
- private installed: Extension[] = [];
-
- constructor(
- readonly server: IExtensionManagementServer,
- private readonly stateProvider: IExtensionStateProvider<ExtensionState>,
- private readonly runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,
- private readonly isWorkspaceServer: boolean,
- @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
- @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
- @IWorkbenchExtensionManagementService private readonly workbenchExtensionManagementService: IWorkbenchExtensionManagementService,
- @ITelemetryService private readonly telemetryService: ITelemetryService,
- @IInstantiationService private readonly instantiationService: IInstantiationService
- ) {
- super();
- this._register(server.extensionManagementService.onInstallExtension(e => this.onInstallExtension(e)));
- this._register(server.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
- this._register(server.extensionManagementService.onUninstallExtension(e => this.onUninstallExtension(e.identifier)));
- this._register(server.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
- this._register(server.extensionManagementService.onDidUpdateExtensionMetadata(e => this.onDidUpdateExtensionMetadata(e.local)));
- this._register(server.extensionManagementService.onDidChangeProfile(() => this.reset()));
- this._register(extensionEnablementService.onEnablementChanged(e => this.onEnablementChanged(e)));
- this._register(Event.any(this.onChange, this.onReset)(() => this._local = undefined));
- if (this.isWorkspaceServer) {
- this._register(this.workbenchExtensionManagementService.onInstallExtension(e => {
- if (e.workspaceScoped) {
- this.onInstallExtension(e);
- }
- }));
- this._register(this.workbenchExtensionManagementService.onDidInstallExtensions(e => {
- const result = e.filter(e => e.workspaceScoped);
- if (result.length) {
- this.onDidInstallExtensions(result);
- }
- }));
- this._register(this.workbenchExtensionManagementService.onUninstallExtension(e => {
- if (e.workspaceScoped) {
- this.onUninstallExtension(e.identifier);
- }
- }));
- this._register(this.workbenchExtensionManagementService.onDidUninstallExtension(e => {
- if (e.workspaceScoped) {
- this.onDidUninstallExtension(e);
- }
- }));
- }
- }
-
- private _local: Extension[] | undefined;
- get local(): Extension[] {
- if (!this._local) {
- this._local = [];
- for (const extension of this.installed) {
- this._local.push(extension);
- }
- for (const extension of this.installing) {
- if (!this.installed.some(installed => areSameExtensions(installed.identifier, extension.identifier))) {
- this._local.push(extension);
- }
- }
- }
- return this._local;
- }
-
- async queryInstalled(productVersion: IProductVersion): Promise<IExtension[]> {
- await this.fetchInstalledExtensions(productVersion);
- this._onChange.fire(undefined);
- return this.local;
- }
-
- async syncInstalledExtensionsWithGallery(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion, flagExtensionsMissingFromGallery?: IExtensionInfo[]): Promise<void> {
- const extensions = await this.mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions, productVersion);
- for (const [extension, gallery] of extensions) {
- // update metadata of the extension if it does not exist
- if (extension.local && !extension.local.identifier.uuid) {
- extension.local = await this.updateMetadata(extension.local, gallery);
- }
- if (!extension.gallery || extension.gallery.version !== gallery.version || extension.gallery.properties.targetPlatform !== gallery.properties.targetPlatform) {
- extension.gallery = gallery;
- this._onChange.fire({ extension });
- }
- }
- // Detect extensions that do not have a corresponding gallery entry.
- if (flagExtensionsMissingFromGallery) {
- const extensionsToQuery = [];
- for (const extension of this.local) {
- // Extension is already paired with a gallery object
- if (extension.gallery) {
- continue;
- }
- // Already flagged as missing from gallery
- if (extension.missingFromGallery) {
- continue;
- }
- // A UUID indicates extension originated from gallery
- if (!extension.identifier.uuid) {
- continue;
- }
- // Extension is not present in the set we are concerned about
- if (!flagExtensionsMissingFromGallery.some(f => areSameExtensions(f, extension.identifier))) {
- continue;
- }
- extensionsToQuery.push(extension);
- }
- if (extensionsToQuery.length) {
- const queryResult = await this.galleryService.getExtensions(extensionsToQuery.map(e => ({ ...e.identifier, version: e.version })), CancellationToken.None);
- const queriedIds: string[] = [];
- const missingIds: string[] = [];
- for (const extension of extensionsToQuery) {
- queriedIds.push(extension.identifier.id);
- const gallery = queryResult.find(g => areSameExtensions(g.identifier, extension.identifier));
- if (gallery) {
- extension.gallery = gallery;
- } else {
- extension.missingFromGallery = true;
- missingIds.push(extension.identifier.id);
- }
- this._onChange.fire({ extension });
- }
- type MissingFromGalleryClassification = {
- owner: 'joshspicer';
- comment: 'Report when installed extensions are no longer available in the gallery';
- queriedIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions queried as potentially missing from gallery' };
- missingIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions determined missing from gallery' };
- };
- type MissingFromGalleryEvent = {
- readonly queriedIds: TelemetryTrustedValue<string>;
- readonly missingIds: TelemetryTrustedValue<string>;
- };
- this.telemetryService.publicLog2<MissingFromGalleryEvent, MissingFromGalleryClassification>('extensions:missingFromGallery', {
- queriedIds: new TelemetryTrustedValue(queriedIds.join(';')),
- missingIds: new TelemetryTrustedValue(missingIds.join(';'))
- });
- }
- }
- }
-
- private async mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise<[Extension, IGalleryExtension][]> {
- const mappedExtensions = this.mapInstalledExtensionWithGalleryExtension(galleryExtensions);
- const targetPlatform = await this.server.extensionManagementService.getTargetPlatform();
- const compatibleGalleryExtensions: IGalleryExtension[] = [];
- const compatibleGalleryExtensionsToFetch: IExtensionInfo[] = [];
- await Promise.allSettled(mappedExtensions.map(async ([extension, gallery]) => {
- if (extension.local) {
- if (await this.galleryService.isExtensionCompatible(gallery, extension.local.preRelease, targetPlatform, productVersion)) {
- compatibleGalleryExtensions.push(gallery);
- } else {
- compatibleGalleryExtensionsToFetch.push({ ...extension.local.identifier, preRelease: extension.local.preRelease });
- }
- }
- }));
- if (compatibleGalleryExtensionsToFetch.length) {
- const result = await this.galleryService.getExtensions(compatibleGalleryExtensionsToFetch, { targetPlatform, compatible: true, queryAllVersions: true, productVersion }, CancellationToken.None);
- compatibleGalleryExtensions.push(...result);
- }
- return this.mapInstalledExtensionWithGalleryExtension(compatibleGalleryExtensions);
- }
-
- private mapInstalledExtensionWithGalleryExtension(galleryExtensions: IGalleryExtension[]): [Extension, IGalleryExtension][] {
- const mappedExtensions: [Extension, IGalleryExtension][] = [];
- const byUUID = new Map<string, IGalleryExtension>(), byID = new Map<string, IGalleryExtension>();
- for (const gallery of galleryExtensions) {
- byUUID.set(gallery.identifier.uuid, gallery);
- byID.set(gallery.identifier.id.toLowerCase(), gallery);
- }
- for (const installed of this.installed) {
- if (installed.uuid) {
- const gallery = byUUID.get(installed.uuid);
- if (gallery) {
- mappedExtensions.push([installed, gallery]);
- continue;
- }
- }
- if (installed.local?.source !== 'resource') {
- const gallery = byID.get(installed.identifier.id.toLowerCase());
- if (gallery) {
- mappedExtensions.push([installed, gallery]);
- }
- }
- }
- return mappedExtensions;
- }
-
- private async updateMetadata(localExtension: ILocalExtension, gallery: IGalleryExtension): Promise<ILocalExtension> {
- let isPreReleaseVersion = false;
- if (localExtension.manifest.version !== gallery.version) {
- type GalleryServiceMatchInstalledExtensionClassification = {
- owner: 'sandy081';
- comment: 'Report when a request is made to update metadata of an installed extension';
- };
- this.telemetryService.publicLog2<{}, GalleryServiceMatchInstalledExtensionClassification>('galleryService:updateMetadata');
- const galleryWithLocalVersion: IGalleryExtension | undefined = (await this.galleryService.getExtensions([{ ...localExtension.identifier, version: localExtension.manifest.version }], CancellationToken.None))[0];
- isPreReleaseVersion = !!galleryWithLocalVersion?.properties?.isPreReleaseVersion;
- }
- return this.workbenchExtensionManagementService.updateMetadata(localExtension, { id: gallery.identifier.uuid, publisherDisplayName: gallery.publisherDisplayName, publisherId: gallery.publisherId, isPreReleaseVersion });
- }
-
- canInstall(galleryExtension: IGalleryExtension): Promise<true | IMarkdownString> {
- return this.server.extensionManagementService.canInstall(galleryExtension);
- }
-
- private onInstallExtension(event: InstallExtensionEvent): void {
- const { source } = event;
- if (source && !URI.isUri(source)) {
- const extension = this.installed.find(e => areSameExtensions(e.identifier, source.identifier))
- ?? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, undefined, source, undefined);
- this.installing.push(extension);
- this._onChange.fire({ extension });
- }
- }
-
- private async fetchInstalledExtensions(productVersion?: IProductVersion): Promise<void> {
- const extensionsControlManifest = await this.server.extensionManagementService.getExtensionsControlManifest();
- const all = await this.server.extensionManagementService.getInstalled(undefined, undefined, productVersion);
- if (this.isWorkspaceServer) {
- all.push(...await this.workbenchExtensionManagementService.getInstalledWorkspaceExtensions(true));
- }
-
- // dedup workspace, user and system extensions by giving priority to workspace first and then to user extension.
- const installed = groupByExtension(all, r => r.identifier).reduce((result, extensions) => {
- if (extensions.length === 1) {
- result.push(extensions[0]);
- } else {
- let workspaceExtension: ILocalExtension | undefined,
- userExtension: ILocalExtension | undefined,
- systemExtension: ILocalExtension | undefined;
- for (const extension of extensions) {
- if (extension.isWorkspaceScoped) {
- workspaceExtension = extension;
- } else if (extension.type === ExtensionType.User) {
- userExtension = extension;
- } else {
- systemExtension = extension;
- }
- }
- const extension = workspaceExtension ?? userExtension ?? systemExtension;
- if (extension) {
- result.push(extension);
- }
- }
- return result;
- }, []);
-
- const byId = index(this.installed, e => e.local ? e.local.identifier.id : e.identifier.id);
- this.installed = installed.map(local => {
- const extension = byId[local.identifier.id] || this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined);
- extension.local = local;
- extension.enablementState = this.extensionEnablementService.getEnablementState(local);
- extension.setExtensionsControlManifest(extensionsControlManifest);
- return extension;
- });
- }
-
- private async reset(): Promise<void> {
- this.installed = [];
- this.installing = [];
- this.uninstalling = [];
- await this.fetchInstalledExtensions();
- this._onReset.fire();
- }
-
- private async onDidInstallExtensions(results: readonly InstallExtensionResult[]): Promise<void> {
- const extensions: Extension[] = [];
- for (const event of results) {
- const { local, source } = event;
- const gallery = source && !URI.isUri(source) ? source : undefined;
- const location = source && URI.isUri(source) ? source : undefined;
- const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] : null;
- this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing;
-
- let extension: Extension | undefined = installingExtension ? installingExtension
- : (location || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined)
- : undefined;
- if (extension) {
- if (local) {
- const installed = this.installed.filter(e => areSameExtensions(e.identifier, extension!.identifier))[0];
- if (installed) {
- extension = installed;
- } else {
- this.installed.push(extension);
- }
- extension.local = local;
- if (!extension.gallery) {
- extension.gallery = gallery;
- }
- extension.enablementState = this.extensionEnablementService.getEnablementState(local);
- }
- extensions.push(extension);
- }
- this._onChange.fire(!local || !extension ? undefined : { extension, operation: event.operation });
- }
-
- if (extensions.length) {
- const manifest = await this.server.extensionManagementService.getExtensionsControlManifest();
- for (const extension of extensions) {
- extension.setExtensionsControlManifest(manifest);
- }
- this.matchInstalledExtensionsWithGallery(extensions);
- }
- }
-
- private async onDidUpdateExtensionMetadata(local: ILocalExtension): Promise<void> {
- const extension = this.installed.find(e => areSameExtensions(e.identifier, local.identifier));
- if (extension?.local) {
- extension.local = local;
- this._onChange.fire({ extension });
- }
- }
-
- private async matchInstalledExtensionsWithGallery(extensions: Extension[]): Promise<void> {
- const toMatch = extensions.filter(e => e.local && !e.gallery && e.local.source !== 'resource');
- if (!toMatch.length) {
- return;
- }
- if (!this.galleryService.isEnabled()) {
- return;
- }
- const galleryExtensions = await this.galleryService.getExtensions(toMatch.map(e => ({ ...e.identifier, preRelease: e.local?.preRelease })), { compatible: true, targetPlatform: await this.server.extensionManagementService.getTargetPlatform() }, CancellationToken.None);
- for (const extension of extensions) {
- const compatible = galleryExtensions.find(e => areSameExtensions(e.identifier, extension.identifier));
- if (compatible) {
- extension.gallery = compatible;
- this._onChange.fire({ extension });
- }
- }
- }
-
- private onUninstallExtension(identifier: IExtensionIdentifier): void {
- const extension = this.installed.filter(e => areSameExtensions(e.identifier, identifier))[0];
- if (extension) {
- const uninstalling = this.uninstalling.filter(e => areSameExtensions(e.identifier, identifier))[0] || extension;
- this.uninstalling = [uninstalling, ...this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier))];
- this._onChange.fire(uninstalling ? { extension: uninstalling } : undefined);
- }
- }
-
- private onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
- const uninstalled = this.uninstalling.find(e => areSameExtensions(e.identifier, identifier)) || this.installed.find(e => areSameExtensions(e.identifier, identifier));
- this.uninstalling = this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier));
- if (!error) {
- this.installed = this.installed.filter(e => !areSameExtensions(e.identifier, identifier));
- }
- if (uninstalled) {
- this._onChange.fire({ extension: uninstalled });
- }
- }
-
- private onEnablementChanged(platformExtensions: readonly IPlatformExtension[]) {
- const extensions = this.local.filter(e => platformExtensions.some(p => areSameExtensions(e.identifier, p.identifier)));
- for (const extension of extensions) {
- if (extension.local) {
- const enablementState = this.extensionEnablementService.getEnablementState(extension.local);
- if (enablementState !== extension.enablementState) {
- extension.enablementState = enablementState;
- this._onChange.fire({ extension });
- }
- }
- }
- }
-
- getExtensionState(extension: Extension): ExtensionState {
- if (extension.gallery && this.installing.some(e => !!e.gallery && areSameExtensions(e.gallery.identifier, extension.gallery!.identifier))) {
- return ExtensionState.Installing;
- }
- if (this.uninstalling.some(e => areSameExtensions(e.identifier, extension.identifier))) {
- return ExtensionState.Uninstalling;
- }
- const local = this.installed.filter(e => e === extension || (e.gallery && extension.gallery && areSameExtensions(e.gallery.identifier, extension.gallery.identifier)))[0];
- return local ? ExtensionState.Installed : ExtensionState.Uninstalled;
- }
-}
-
export class ExtensionsWorkbenchService extends Disposable implements IExtensionsWorkbenchService, IURLHandler {
private static readonly UpdatesCheckInterval = 1000 * 60 * 60 * 12; // 12 hours