Path: blob/main/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
5248 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as nls from '../../../../nls.js';6import * as semver from '../../../../base/common/semver/semver.js';7import { Event, Emitter } from '../../../../base/common/event.js';8import { index } from '../../../../base/common/arrays.js';9import { CancelablePromise, Promises, ThrottledDelayer, createCancelablePromise } from '../../../../base/common/async.js';10import { CancellationError, getErrorMessage, isCancellationError } from '../../../../base/common/errors.js';11import { Disposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';12import { IPager, singlePagePager } from '../../../../base/common/paging.js';13import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';14import {15IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,16InstallExtensionEvent, DidUninstallExtensionEvent, InstallOperation, WEB_EXTENSION_TAG, InstallExtensionResult,17IExtensionsControlManifest, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible, InstallExtensionInfo, EXTENSION_IDENTIFIER_REGEX,18InstallOptions, IProductVersion,19UninstallExtensionInfo,20TargetPlatformToString,21IAllowedExtensionsService,22AllowedExtensionsConfigKey,23EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT,24ExtensionManagementError,25ExtensionManagementErrorCode,26MaliciousExtensionInfo,27shouldRequireRepositorySignatureFor,28IGalleryExtensionVersion29} from '../../../../platform/extensionManagement/common/extensionManagement.js';30import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, IResourceExtension } from '../../../services/extensionManagement/common/extensionManagement.js';31import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, getGalleryExtensionId, findMatchingMaliciousEntry } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';32import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';33import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';34import { IHostService } from '../../../services/host/browser/host.js';35import { URI } from '../../../../base/common/uri.js';36import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions, ExtensionRuntimeState, ExtensionRuntimeActionType, AutoRestartConfigurationKey, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionsNotification } from '../common/extensions.js';37import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js';38import { IURLService, IURLHandler, IOpenURLOptions } from '../../../../platform/url/common/url.js';39import { ExtensionsInput, IExtensionEditorOptions } from '../common/extensionsInput.js';40import { ILogService } from '../../../../platform/log/common/log.js';41import { IProgressOptions, IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';42import { INotificationService, NotificationPriority, Severity } from '../../../../platform/notification/common/notification.js';43import * as resources from '../../../../base/common/resources.js';44import { CancellationToken } from '../../../../base/common/cancellation.js';45import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';46import { IFileService } from '../../../../platform/files/common/files.js';47import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, TargetPlatform, ExtensionIdentifier, IExtensionIdentifier, IExtensionDescription, isApplicationScopedExtension } from '../../../../platform/extensions/common/extensions.js';48import { ILanguageService } from '../../../../editor/common/languages/language.js';49import { IProductService } from '../../../../platform/product/common/productService.js';50import { FileAccess } from '../../../../base/common/network.js';51import { IIgnoredExtensionsManagementService } from '../../../../platform/userDataSync/common/ignoredExtensions.js';52import { IUserDataAutoSyncService, IUserDataSyncEnablementService, SyncResource } from '../../../../platform/userDataSync/common/userDataSync.js';53import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';54import { isBoolean, isDefined, isString, isUndefined } from '../../../../base/common/types.js';55import { IExtensionManifestPropertiesService } from '../../../services/extensions/common/extensionManifestPropertiesService.js';56import { IExtensionService, IExtensionsStatus as IExtensionRuntimeStatus, toExtension, toExtensionDescription } from '../../../services/extensions/common/extensions.js';57import { isWeb, language } from '../../../../base/common/platform.js';58import { getLocale } from '../../../../platform/languagePacks/common/languagePacks.js';59import { ILocaleService } from '../../../services/localization/common/locale.js';60import { TelemetryTrustedValue } from '../../../../platform/telemetry/common/telemetryUtils.js';61import { ILifecycleService, LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';62import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js';63import { mainWindow } from '../../../../base/browser/window.js';64import { IDialogService, IFileDialogService, IPromptButton } from '../../../../platform/dialogs/common/dialogs.js';65import { IUpdateService, StateType } from '../../../../platform/update/common/update.js';66import { areApiProposalsCompatible, isEngineValid } from '../../../../platform/extensions/common/extensionValidator.js';67import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';68import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';69import { ShowCurrentReleaseNotesActionId } from '../../update/common/update.js';70import { IViewsService } from '../../../services/views/common/viewsService.js';71import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';72import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';73import { ExtensionGalleryResourceType, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';74import { fromNow } from '../../../../base/common/date.js';75import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';76import { IMeteredConnectionService } from '../../../../platform/meteredConnection/common/meteredConnection.js';7778interface IExtensionStateProvider<T> {79(extension: Extension): T;80}8182interface InstalledExtensionsEvent {83readonly extensionIds: TelemetryTrustedValue<string>;84readonly count: number;85}86type ExtensionsLoadClassification = {87owner: 'digitarald';88comment: 'Helps to understand which extensions are the most actively used.';89readonly extensionIds: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The list of extension ids that are installed.' };90readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The number of extensions that are installed.' };91};9293export class Extension implements IExtension {9495public enablementState: EnablementState = EnablementState.EnabledGlobally;9697private galleryResourcesCache = new Map<string, any>();9899private _missingFromGallery: boolean | undefined;100101constructor(102private stateProvider: IExtensionStateProvider<ExtensionState>,103private runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,104public readonly server: IExtensionManagementServer | undefined,105public local: ILocalExtension | undefined,106private _gallery: IGalleryExtension | undefined,107private readonly resourceExtensionInfo: { resourceExtension: IResourceExtension; isWorkspaceScoped: boolean } | undefined,108@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,109@ITelemetryService private readonly telemetryService: ITelemetryService,110@ILogService private readonly logService: ILogService,111@IFileService private readonly fileService: IFileService,112@IProductService private readonly productService: IProductService113) {114}115116get resourceExtension(): IResourceExtension | undefined {117if (this.resourceExtensionInfo) {118return this.resourceExtensionInfo.resourceExtension;119}120if (this.local?.isWorkspaceScoped) {121return {122type: 'resource',123identifier: this.local.identifier,124location: this.local.location,125manifest: this.local.manifest,126changelogUri: this.local.changelogUrl,127readmeUri: this.local.readmeUrl,128};129}130return undefined;131}132133get gallery(): IGalleryExtension | undefined {134return this._gallery;135}136137set gallery(gallery: IGalleryExtension | undefined) {138this._gallery = gallery;139this.galleryResourcesCache.clear();140}141142get missingFromGallery(): boolean {143return !!this._missingFromGallery;144}145146set missingFromGallery(missing: boolean) {147this._missingFromGallery = missing;148}149150get type(): ExtensionType {151return this.local ? this.local.type : ExtensionType.User;152}153154get isBuiltin(): boolean {155return this.local ? this.local.isBuiltin : false;156}157158get isWorkspaceScoped(): boolean {159if (this.local) {160return this.local.isWorkspaceScoped;161}162if (this.resourceExtensionInfo) {163return this.resourceExtensionInfo.isWorkspaceScoped;164}165return false;166}167168get name(): string {169if (this.gallery) {170return this.gallery.name;171}172return this.getManifestFromLocalOrResource()?.name ?? '';173}174175get displayName(): string {176if (this.gallery) {177return this.gallery.displayName || this.gallery.name;178}179180return this.getManifestFromLocalOrResource()?.displayName ?? this.name;181}182183get identifier(): IExtensionIdentifier {184if (this.gallery) {185return this.gallery.identifier;186}187if (this.resourceExtension) {188return this.resourceExtension.identifier;189}190return this.local?.identifier ?? { id: '' };191}192193get uuid(): string | undefined {194return this.gallery ? this.gallery.identifier.uuid : this.local?.identifier.uuid;195}196197get publisher(): string {198if (this.gallery) {199return this.gallery.publisher;200}201return this.getManifestFromLocalOrResource()?.publisher ?? '';202}203204get publisherDisplayName(): string {205if (this.gallery) {206return this.gallery.publisherDisplayName || this.gallery.publisher;207}208209if (this.local?.publisherDisplayName) {210return this.local.publisherDisplayName;211}212213return this.publisher;214}215216get publisherUrl(): URI | undefined {217return this.gallery?.publisherLink ? URI.parse(this.gallery.publisherLink) : undefined;218}219220get publisherDomain(): { link: string; verified: boolean } | undefined {221return this.gallery?.publisherDomain;222}223224get publisherSponsorLink(): URI | undefined {225return this.gallery?.publisherSponsorLink ? URI.parse(this.gallery.publisherSponsorLink) : undefined;226}227228get version(): string {229return this.local ? this.local.manifest.version : this.latestVersion;230}231232get private(): boolean {233return this.gallery ? this.gallery.private : this.local ? this.local.private : false;234}235236get pinned(): boolean {237return !!this.local?.pinned;238}239240get latestVersion(): string {241return this.gallery ? this.gallery.version : this.getManifestFromLocalOrResource()?.version ?? '';242}243244get description(): string {245return this.gallery ? this.gallery.description : this.getManifestFromLocalOrResource()?.description ?? '';246}247248get url(): string | undefined {249return this.gallery?.detailsLink;250}251252get iconUrl(): string | undefined {253return this.galleryIconUrl || this.resourceExtensionIconUrl || this.localIconUrl || this.defaultIconUrl;254}255256get iconUrlFallback(): string | undefined {257return this.gallery?.assets.icon?.fallbackUri;258}259260private get localIconUrl(): string | undefined {261if (this.local && this.local.manifest.icon) {262return FileAccess.uriToBrowserUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true);263}264return undefined;265}266267private get resourceExtensionIconUrl(): string | undefined {268if (this.resourceExtension?.manifest.icon) {269return FileAccess.uriToBrowserUri(resources.joinPath(this.resourceExtension.location, this.resourceExtension.manifest.icon)).toString(true);270}271return undefined;272}273274private get galleryIconUrl(): string | undefined {275return this.gallery?.assets.icon?.uri;276}277278private get defaultIconUrl(): string | undefined {279if (this.type === ExtensionType.System && this.local) {280if (this.local.manifest && this.local.manifest.contributes) {281if (Array.isArray(this.local.manifest.contributes.themes) && this.local.manifest.contributes.themes.length) {282return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/theme-icon.png').toString(true);283}284if (Array.isArray(this.local.manifest.contributes.grammars) && this.local.manifest.contributes.grammars.length) {285return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/language-icon.svg').toString(true);286}287}288}289return undefined;290}291292get repository(): string | undefined {293return this.gallery && this.gallery.assets.repository ? this.gallery.assets.repository.uri : undefined;294}295296get licenseUrl(): string | undefined {297return this.gallery && this.gallery.assets.license ? this.gallery.assets.license.uri : undefined;298}299300get supportUrl(): string | undefined {301return this.gallery && this.gallery.supportLink ? this.gallery.supportLink : undefined;302}303304get state(): ExtensionState {305return this.stateProvider(this);306}307308private malicious: MaliciousExtensionInfo | undefined;309public get isMalicious(): boolean | undefined {310return !!this.malicious || this.enablementState === EnablementState.DisabledByMalicious;311}312313public get maliciousInfoLink(): string | undefined {314return this.malicious?.learnMoreLink;315}316317public deprecationInfo: IDeprecationInfo | undefined;318319get installCount(): number | undefined {320return this.gallery ? this.gallery.installCount : undefined;321}322323get rating(): number | undefined {324return this.gallery ? this.gallery.rating : undefined;325}326327get ratingCount(): number | undefined {328return this.gallery ? this.gallery.ratingCount : undefined;329}330331get ratingUrl(): string | undefined {332return this.gallery?.ratingLink;333}334335get outdated(): boolean {336try {337if (!this.gallery || !this.local) {338return false;339}340// Do not allow updating system extensions in stable341if (this.type === ExtensionType.System && this.productService.quality === 'stable') {342return false;343}344if (!this.local.preRelease && this.gallery.properties.isPreReleaseVersion) {345return false;346}347if (semver.gt(this.latestVersion, this.version)) {348return true;349}350if (this.outdatedTargetPlatform) {351return true;352}353} catch (error) {354/* Ignore */355}356return false;357}358359get outdatedTargetPlatform(): boolean {360return !!this.local && !!this.gallery361&& ![TargetPlatform.UNDEFINED, TargetPlatform.WEB].includes(this.local.targetPlatform)362&& this.gallery.properties.targetPlatform !== TargetPlatform.WEB363&& this.local.targetPlatform !== this.gallery.properties.targetPlatform364&& semver.eq(this.latestVersion, this.version);365}366367get runtimeState(): ExtensionRuntimeState | undefined {368return this.runtimeStateProvider(this);369}370371get telemetryData(): any {372const { local, gallery } = this;373374if (gallery) {375return getGalleryExtensionTelemetryData(gallery);376} else if (local) {377return getLocalExtensionTelemetryData(local);378} else {379return {};380}381}382383get preview(): boolean {384return this.local?.manifest.preview ?? this.gallery?.preview ?? false;385}386387get preRelease(): boolean {388return !!this.local?.preRelease;389}390391get isPreReleaseVersion(): boolean {392if (this.local) {393return this.local.isPreReleaseVersion;394}395return !!this.gallery?.properties.isPreReleaseVersion;396}397398get hasPreReleaseVersion(): boolean {399return this.gallery ? this.gallery.hasPreReleaseVersion : !!this.local?.hasPreReleaseVersion;400}401402get hasReleaseVersion(): boolean {403return !!this.resourceExtension || !!this.gallery?.hasReleaseVersion;404}405406private getLocal(): ILocalExtension | undefined {407return this.local && !this.outdated ? this.local : undefined;408}409410async getManifest(token: CancellationToken): Promise<IExtensionManifest | null> {411const local = this.getLocal();412if (local) {413return local.manifest;414}415416if (this.gallery) {417return this.getGalleryManifest(token);418}419420if (this.resourceExtension) {421return this.resourceExtension.manifest;422}423424return null;425}426427async getGalleryManifest(token: CancellationToken = CancellationToken.None): Promise<IExtensionManifest | null> {428if (this.gallery) {429let cache = this.galleryResourcesCache.get('manifest');430if (!cache) {431if (this.gallery.assets.manifest) {432this.galleryResourcesCache.set('manifest', cache = this.galleryService.getManifest(this.gallery, token)433.catch(e => {434this.galleryResourcesCache.delete('manifest');435throw e;436}));437} else {438this.logService.error(nls.localize('Manifest is not found', "Manifest is not found"), this.identifier.id);439}440}441return cache;442}443return null;444}445446hasReadme(): boolean {447if (this.local && this.local.readmeUrl) {448return true;449}450451if (this.gallery && this.gallery.assets.readme) {452return true;453}454455if (this.resourceExtension?.readmeUri) {456return true;457}458459return this.type === ExtensionType.System;460}461462async getReadme(token: CancellationToken): Promise<string> {463const local = this.getLocal();464if (local?.readmeUrl) {465const content = await this.fileService.readFile(local.readmeUrl);466return content.value.toString();467}468469if (this.gallery) {470if (this.gallery.assets.readme) {471return this.galleryService.getReadme(this.gallery, token);472}473this.telemetryService.publicLog('extensions:NotFoundReadMe', this.telemetryData);474}475476if (this.type === ExtensionType.System) {477return Promise.resolve(`# ${this.displayName || this.name}478**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.479## Features480${this.description}481`);482}483484if (this.resourceExtension?.readmeUri) {485const content = await this.fileService.readFile(this.resourceExtension?.readmeUri);486return content.value.toString();487}488489return Promise.reject(new Error('not available'));490}491492hasChangelog(): boolean {493if (this.local && this.local.changelogUrl) {494return true;495}496497if (this.gallery && this.gallery.assets.changelog) {498return true;499}500501return this.type === ExtensionType.System;502}503504async getChangelog(token: CancellationToken): Promise<string> {505const local = this.getLocal();506if (local?.changelogUrl) {507const content = await this.fileService.readFile(local.changelogUrl);508return content.value.toString();509}510511if (this.gallery?.assets.changelog) {512return this.galleryService.getChangelog(this.gallery, token);513}514515if (this.type === ExtensionType.System) {516return Promise.resolve(`Please check the [VS Code Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`);517}518519return Promise.reject(new Error('not available'));520}521522get categories(): readonly string[] {523const { local, gallery, resourceExtension } = this;524if (local && local.manifest.categories && !this.outdated) {525return local.manifest.categories;526}527if (gallery) {528return gallery.categories;529}530if (resourceExtension) {531return resourceExtension.manifest.categories ?? [];532}533return [];534}535536get tags(): readonly string[] {537const { gallery } = this;538if (gallery) {539return gallery.tags.filter(tag => !tag.startsWith('_'));540}541return [];542}543544get dependencies(): string[] {545const { local, gallery, resourceExtension } = this;546if (local && local.manifest.extensionDependencies && !this.outdated) {547return local.manifest.extensionDependencies;548}549if (gallery) {550return gallery.properties.dependencies || [];551}552if (resourceExtension) {553return resourceExtension.manifest.extensionDependencies || [];554}555return [];556}557558get extensionPack(): string[] {559const { local, gallery, resourceExtension } = this;560if (local && local.manifest.extensionPack && !this.outdated) {561return local.manifest.extensionPack;562}563if (gallery) {564return gallery.properties.extensionPack || [];565}566if (resourceExtension) {567return resourceExtension.manifest.extensionPack || [];568}569return [];570}571572setExtensionsControlManifest(extensionsControlManifest: IExtensionsControlManifest): void {573this.malicious = findMatchingMaliciousEntry(this.identifier, extensionsControlManifest.malicious);574this.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[this.identifier.id.toLowerCase()] : undefined;575}576577private getManifestFromLocalOrResource(): IExtensionManifest | null {578if (this.local) {579return this.local.manifest;580}581if (this.resourceExtension) {582return this.resourceExtension.manifest;583}584return null;585}586}587588const EXTENSIONS_AUTO_UPDATE_KEY = 'extensions.autoUpdate';589const EXTENSIONS_DONOT_AUTO_UPDATE_KEY = 'extensions.donotAutoUpdate';590const EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY = 'extensions.dismissedNotifications';591592class Extensions extends Disposable {593594private readonly _onChange = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>());595get onChange() { return this._onChange.event; }596597private readonly _onReset = this._register(new Emitter<void>());598get onReset() { return this._onReset.event; }599600private installing: Extension[] = [];601private uninstalling: Extension[] = [];602private installed: Extension[] = [];603604constructor(605readonly server: IExtensionManagementServer,606private readonly stateProvider: IExtensionStateProvider<ExtensionState>,607private readonly runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,608private readonly isWorkspaceServer: boolean,609@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,610@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,611@IWorkbenchExtensionManagementService private readonly workbenchExtensionManagementService: IWorkbenchExtensionManagementService,612@ITelemetryService private readonly telemetryService: ITelemetryService,613@IInstantiationService private readonly instantiationService: IInstantiationService614) {615super();616this._register(server.extensionManagementService.onInstallExtension(e => this.onInstallExtension(e)));617this._register(server.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));618this._register(server.extensionManagementService.onUninstallExtension(e => this.onUninstallExtension(e.identifier)));619this._register(server.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));620this._register(server.extensionManagementService.onDidUpdateExtensionMetadata(e => this.onDidUpdateExtensionMetadata(e.local)));621this._register(server.extensionManagementService.onDidChangeProfile(() => this.reset()));622this._register(extensionEnablementService.onEnablementChanged(e => this.onEnablementChanged(e)));623this._register(Event.any(this.onChange, this.onReset)(() => this._local = undefined));624if (this.isWorkspaceServer) {625this._register(this.workbenchExtensionManagementService.onInstallExtension(e => {626if (e.workspaceScoped) {627this.onInstallExtension(e);628}629}));630this._register(this.workbenchExtensionManagementService.onDidInstallExtensions(e => {631const result = e.filter(e => e.workspaceScoped);632if (result.length) {633this.onDidInstallExtensions(result);634}635}));636this._register(this.workbenchExtensionManagementService.onUninstallExtension(e => {637if (e.workspaceScoped) {638this.onUninstallExtension(e.identifier);639}640}));641this._register(this.workbenchExtensionManagementService.onDidUninstallExtension(e => {642if (e.workspaceScoped) {643this.onDidUninstallExtension(e);644}645}));646}647}648649private _local: Extension[] | undefined;650get local(): Extension[] {651if (!this._local) {652this._local = [];653for (const extension of this.installed) {654this._local.push(extension);655}656for (const extension of this.installing) {657if (!this.installed.some(installed => areSameExtensions(installed.identifier, extension.identifier))) {658this._local.push(extension);659}660}661}662return this._local;663}664665async queryInstalled(productVersion: IProductVersion): Promise<IExtension[]> {666await this.fetchInstalledExtensions(productVersion);667this._onChange.fire(undefined);668return this.local;669}670671async syncInstalledExtensionsWithGallery(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion, flagExtensionsMissingFromGallery?: IExtensionInfo[]): Promise<void> {672const extensions = await this.mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions, productVersion);673for (const [extension, gallery] of extensions) {674// update metadata of the extension if it does not exist675if (extension.local && !extension.local.identifier.uuid) {676extension.local = await this.updateMetadata(extension.local, gallery);677}678if (!extension.gallery || extension.gallery.version !== gallery.version || extension.gallery.properties.targetPlatform !== gallery.properties.targetPlatform) {679extension.gallery = gallery;680this._onChange.fire({ extension });681}682}683// Detect extensions that do not have a corresponding gallery entry.684if (flagExtensionsMissingFromGallery) {685const extensionsToQuery = [];686for (const extension of this.local) {687// Extension is already paired with a gallery object688if (extension.gallery) {689continue;690}691// Already flagged as missing from gallery692if (extension.missingFromGallery) {693continue;694}695// A UUID indicates extension originated from gallery696if (!extension.identifier.uuid) {697continue;698}699// Extension is not present in the set we are concerned about700if (!flagExtensionsMissingFromGallery.some(f => areSameExtensions(f, extension.identifier))) {701continue;702}703extensionsToQuery.push(extension);704}705if (extensionsToQuery.length) {706const queryResult = await this.galleryService.getExtensions(extensionsToQuery.map(e => ({ ...e.identifier, version: e.version })), CancellationToken.None);707const queriedIds: string[] = [];708const missingIds: string[] = [];709for (const extension of extensionsToQuery) {710queriedIds.push(extension.identifier.id);711const gallery = queryResult.find(g => areSameExtensions(g.identifier, extension.identifier));712if (gallery) {713extension.gallery = gallery;714} else {715extension.missingFromGallery = true;716missingIds.push(extension.identifier.id);717}718this._onChange.fire({ extension });719}720type MissingFromGalleryClassification = {721owner: 'joshspicer';722comment: 'Report when installed extensions are no longer available in the gallery';723queriedIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions queried as potentially missing from gallery' };724missingIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions determined missing from gallery' };725};726type MissingFromGalleryEvent = {727readonly queriedIds: TelemetryTrustedValue<string>;728readonly missingIds: TelemetryTrustedValue<string>;729};730this.telemetryService.publicLog2<MissingFromGalleryEvent, MissingFromGalleryClassification>('extensions:missingFromGallery', {731queriedIds: new TelemetryTrustedValue(queriedIds.join(';')),732missingIds: new TelemetryTrustedValue(missingIds.join(';'))733});734}735}736}737738private async mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise<[Extension, IGalleryExtension][]> {739const mappedExtensions = this.mapInstalledExtensionWithGalleryExtension(galleryExtensions);740const targetPlatform = await this.server.extensionManagementService.getTargetPlatform();741const compatibleGalleryExtensions: IGalleryExtension[] = [];742const compatibleGalleryExtensionsToFetch: IExtensionInfo[] = [];743await Promise.allSettled(mappedExtensions.map(async ([extension, gallery]) => {744if (extension.local) {745if (await this.galleryService.isExtensionCompatible(gallery, extension.local.preRelease, targetPlatform, productVersion)) {746compatibleGalleryExtensions.push(gallery);747} else {748compatibleGalleryExtensionsToFetch.push({ ...extension.local.identifier, preRelease: extension.local.preRelease });749}750}751}));752if (compatibleGalleryExtensionsToFetch.length) {753const result = await this.galleryService.getExtensions(compatibleGalleryExtensionsToFetch, { targetPlatform, compatible: true, queryAllVersions: true, productVersion }, CancellationToken.None);754compatibleGalleryExtensions.push(...result);755}756return this.mapInstalledExtensionWithGalleryExtension(compatibleGalleryExtensions);757}758759private mapInstalledExtensionWithGalleryExtension(galleryExtensions: IGalleryExtension[]): [Extension, IGalleryExtension][] {760const mappedExtensions: [Extension, IGalleryExtension][] = [];761const byUUID = new Map<string, IGalleryExtension>(), byID = new Map<string, IGalleryExtension>();762for (const gallery of galleryExtensions) {763byUUID.set(gallery.identifier.uuid, gallery);764byID.set(gallery.identifier.id.toLowerCase(), gallery);765}766for (const installed of this.installed) {767if (installed.uuid) {768const gallery = byUUID.get(installed.uuid);769if (gallery) {770mappedExtensions.push([installed, gallery]);771continue;772}773}774if (installed.local?.source !== 'resource') {775const gallery = byID.get(installed.identifier.id.toLowerCase());776if (gallery) {777mappedExtensions.push([installed, gallery]);778}779}780}781return mappedExtensions;782}783784private async updateMetadata(localExtension: ILocalExtension, gallery: IGalleryExtension): Promise<ILocalExtension> {785let isPreReleaseVersion = false;786if (localExtension.manifest.version !== gallery.version) {787type GalleryServiceMatchInstalledExtensionClassification = {788owner: 'sandy081';789comment: 'Report when a request is made to update metadata of an installed extension';790};791this.telemetryService.publicLog2<{}, GalleryServiceMatchInstalledExtensionClassification>('galleryService:updateMetadata');792const galleryWithLocalVersion: IGalleryExtension | undefined = (await this.galleryService.getExtensions([{ ...localExtension.identifier, version: localExtension.manifest.version }], CancellationToken.None))[0];793isPreReleaseVersion = !!galleryWithLocalVersion?.properties?.isPreReleaseVersion;794}795return this.workbenchExtensionManagementService.updateMetadata(localExtension, { id: gallery.identifier.uuid, publisherDisplayName: gallery.publisherDisplayName, publisherId: gallery.publisherId, isPreReleaseVersion });796}797798canInstall(galleryExtension: IGalleryExtension): Promise<true | IMarkdownString> {799return this.server.extensionManagementService.canInstall(galleryExtension);800}801802private onInstallExtension(event: InstallExtensionEvent): void {803const { source } = event;804if (source && !URI.isUri(source)) {805const extension = this.installed.find(e => areSameExtensions(e.identifier, source.identifier))806?? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, undefined, source, undefined);807this.installing.push(extension);808this._onChange.fire({ extension });809}810}811812private async fetchInstalledExtensions(productVersion?: IProductVersion): Promise<void> {813const extensionsControlManifest = await this.server.extensionManagementService.getExtensionsControlManifest();814const all = await this.server.extensionManagementService.getInstalled(undefined, undefined, productVersion);815if (this.isWorkspaceServer) {816all.push(...await this.workbenchExtensionManagementService.getInstalledWorkspaceExtensions(true));817}818819// dedup workspace, user and system extensions by giving priority to workspace first and then to user extension.820const installed = groupByExtension(all, r => r.identifier).reduce((result, extensions) => {821if (extensions.length === 1) {822result.push(extensions[0]);823} else {824let workspaceExtension: ILocalExtension | undefined,825userExtension: ILocalExtension | undefined,826systemExtension: ILocalExtension | undefined;827for (const extension of extensions) {828if (extension.isWorkspaceScoped) {829workspaceExtension = extension;830} else if (extension.type === ExtensionType.User) {831userExtension = extension;832} else {833systemExtension = extension;834}835}836const extension = workspaceExtension ?? userExtension ?? systemExtension;837if (extension) {838result.push(extension);839}840}841return result;842}, []);843844const byId = index(this.installed, e => e.local ? e.local.identifier.id : e.identifier.id);845this.installed = installed.map(local => {846const extension = byId[local.identifier.id] || this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined);847extension.local = local;848extension.enablementState = this.extensionEnablementService.getEnablementState(local);849extension.setExtensionsControlManifest(extensionsControlManifest);850return extension;851});852}853854private async reset(): Promise<void> {855this.installed = [];856this.installing = [];857this.uninstalling = [];858await this.fetchInstalledExtensions();859this._onReset.fire();860}861862private async onDidInstallExtensions(results: readonly InstallExtensionResult[]): Promise<void> {863const extensions: Extension[] = [];864for (const event of results) {865const { local, source } = event;866const gallery = source && !URI.isUri(source) ? source : undefined;867const location = source && URI.isUri(source) ? source : undefined;868const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] : null;869this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing;870871let extension: Extension | undefined = installingExtension ? installingExtension872: (location || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined)873: undefined;874if (extension) {875if (local) {876const installed = this.installed.filter(e => areSameExtensions(e.identifier, extension!.identifier))[0];877if (installed) {878extension = installed;879} else {880this.installed.push(extension);881}882extension.local = local;883if (!extension.gallery) {884extension.gallery = gallery;885}886extension.enablementState = this.extensionEnablementService.getEnablementState(local);887}888extensions.push(extension);889}890this._onChange.fire(!local || !extension ? undefined : { extension, operation: event.operation });891}892893if (extensions.length) {894const manifest = await this.server.extensionManagementService.getExtensionsControlManifest();895for (const extension of extensions) {896extension.setExtensionsControlManifest(manifest);897}898this.matchInstalledExtensionsWithGallery(extensions);899}900}901902private async onDidUpdateExtensionMetadata(local: ILocalExtension): Promise<void> {903const extension = this.installed.find(e => areSameExtensions(e.identifier, local.identifier));904if (extension?.local) {905extension.local = local;906this._onChange.fire({ extension });907}908}909910private async matchInstalledExtensionsWithGallery(extensions: Extension[]): Promise<void> {911const toMatch = extensions.filter(e => e.local && !e.gallery && e.local.source !== 'resource');912if (!toMatch.length) {913return;914}915if (!this.galleryService.isEnabled()) {916return;917}918const galleryExtensions = await this.galleryService.getExtensions(toMatch.map(e => ({ ...e.identifier, preRelease: e.local?.preRelease })), { compatible: true, targetPlatform: await this.server.extensionManagementService.getTargetPlatform() }, CancellationToken.None);919for (const extension of extensions) {920const compatible = galleryExtensions.find(e => areSameExtensions(e.identifier, extension.identifier));921if (compatible) {922extension.gallery = compatible;923this._onChange.fire({ extension });924}925}926}927928private onUninstallExtension(identifier: IExtensionIdentifier): void {929const extension = this.installed.filter(e => areSameExtensions(e.identifier, identifier))[0];930if (extension) {931const uninstalling = this.uninstalling.filter(e => areSameExtensions(e.identifier, identifier))[0] || extension;932this.uninstalling = [uninstalling, ...this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier))];933this._onChange.fire(uninstalling ? { extension: uninstalling } : undefined);934}935}936937private onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {938const uninstalled = this.uninstalling.find(e => areSameExtensions(e.identifier, identifier)) || this.installed.find(e => areSameExtensions(e.identifier, identifier));939this.uninstalling = this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier));940if (!error) {941this.installed = this.installed.filter(e => !areSameExtensions(e.identifier, identifier));942}943if (uninstalled) {944this._onChange.fire({ extension: uninstalled });945}946}947948private onEnablementChanged(platformExtensions: readonly IPlatformExtension[]) {949const extensions = this.local.filter(e => platformExtensions.some(p => areSameExtensions(e.identifier, p.identifier)));950for (const extension of extensions) {951if (extension.local) {952const enablementState = this.extensionEnablementService.getEnablementState(extension.local);953if (enablementState !== extension.enablementState) {954extension.enablementState = enablementState;955this._onChange.fire({ extension });956}957}958}959}960961getExtensionState(extension: Extension): ExtensionState {962if (extension.gallery && this.installing.some(e => !!e.gallery && areSameExtensions(e.gallery.identifier, extension.gallery!.identifier))) {963return ExtensionState.Installing;964}965if (this.uninstalling.some(e => areSameExtensions(e.identifier, extension.identifier))) {966return ExtensionState.Uninstalling;967}968const local = this.installed.filter(e => e === extension || (e.gallery && extension.gallery && areSameExtensions(e.gallery.identifier, extension.gallery.identifier)))[0];969return local ? ExtensionState.Installed : ExtensionState.Uninstalled;970}971}972973export class ExtensionsWorkbenchService extends Disposable implements IExtensionsWorkbenchService, IURLHandler {974975private static readonly UpdatesCheckInterval = 1000 * 60 * 60 * 12; // 12 hours976declare readonly _serviceBrand: undefined;977978private hasOutdatedExtensionsContextKey: IContextKey<boolean>;979980private readonly localExtensions: Extensions | null = null;981private readonly remoteExtensions: Extensions | null = null;982private readonly webExtensions: Extensions | null = null;983private readonly extensionsServers: Extensions[] = [];984985private updatesCheckDelayer: ThrottledDelayer<void>;986private autoUpdateDelayer: ThrottledDelayer<void>;987988private readonly _onChange = this._register(new Emitter<IExtension | undefined>());989get onChange(): Event<IExtension | undefined> { return this._onChange.event; }990991private extensionsNotification: IExtensionsNotification & { readonly key: string } | undefined;992private readonly _onDidChangeExtensionsNotification = this._register(new Emitter<IExtensionsNotification | undefined>());993readonly onDidChangeExtensionsNotification = this._onDidChangeExtensionsNotification.event;994995private readonly _onReset = this._register(new Emitter<void>());996get onReset() { return this._onReset.event; }997998private installing: IExtension[] = [];999private tasksInProgress: CancelablePromise<any>[] = [];10001001readonly whenInitialized: Promise<void>;10021003constructor(1004@IInstantiationService private readonly instantiationService: IInstantiationService,1005@IEditorService private readonly editorService: IEditorService,1006@IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService,1007@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,1008@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService,1009@IConfigurationService private readonly configurationService: IConfigurationService,1010@ITelemetryService private readonly telemetryService: ITelemetryService,1011@INotificationService private readonly notificationService: INotificationService,1012@IURLService urlService: IURLService,1013@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,1014@IHostService private readonly hostService: IHostService,1015@IProgressService private readonly progressService: IProgressService,1016@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,1017@ILanguageService private readonly languageService: ILanguageService,1018@IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService,1019@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,1020@IProductService private readonly productService: IProductService,1021@IContextKeyService contextKeyService: IContextKeyService,1022@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,1023@ILogService private readonly logService: ILogService,1024@IExtensionService private readonly extensionService: IExtensionService,1025@ILocaleService private readonly localeService: ILocaleService,1026@ILifecycleService private readonly lifecycleService: ILifecycleService,1027@IFileService private readonly fileService: IFileService,1028@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,1029@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,1030@IStorageService private readonly storageService: IStorageService,1031@IDialogService private readonly dialogService: IDialogService,1032@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,1033@IUpdateService private readonly updateService: IUpdateService,1034@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,1035@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,1036@IViewsService private readonly viewsService: IViewsService,1037@IFileDialogService private readonly fileDialogService: IFileDialogService,1038@IQuickInputService private readonly quickInputService: IQuickInputService,1039@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,1040@IMeteredConnectionService private readonly meteredConnectionService: IMeteredConnectionService1041) {1042super();10431044this.hasOutdatedExtensionsContextKey = HasOutdatedExtensionsContext.bindTo(contextKeyService);1045if (extensionManagementServerService.localExtensionManagementServer) {1046this.localExtensions = this._register(instantiationService.createInstance(Extensions,1047extensionManagementServerService.localExtensionManagementServer,1048ext => this.getExtensionState(ext),1049ext => this.getRuntimeState(ext),1050!extensionManagementServerService.remoteExtensionManagementServer1051));1052this._register(this.localExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));1053this._register(this.localExtensions.onReset(e => this.reset()));1054this.extensionsServers.push(this.localExtensions);1055}1056if (extensionManagementServerService.remoteExtensionManagementServer) {1057this.remoteExtensions = this._register(instantiationService.createInstance(Extensions,1058extensionManagementServerService.remoteExtensionManagementServer,1059ext => this.getExtensionState(ext),1060ext => this.getRuntimeState(ext),1061true1062));1063this._register(this.remoteExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));1064this._register(this.remoteExtensions.onReset(e => this.reset()));1065this.extensionsServers.push(this.remoteExtensions);1066}1067if (extensionManagementServerService.webExtensionManagementServer) {1068this.webExtensions = this._register(instantiationService.createInstance(Extensions,1069extensionManagementServerService.webExtensionManagementServer,1070ext => this.getExtensionState(ext),1071ext => this.getRuntimeState(ext),1072!(extensionManagementServerService.remoteExtensionManagementServer || extensionManagementServerService.localExtensionManagementServer)1073));1074this._register(this.webExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));1075this._register(this.webExtensions.onReset(e => this.reset()));1076this.extensionsServers.push(this.webExtensions);1077}10781079this.updatesCheckDelayer = new ThrottledDelayer<void>(ExtensionsWorkbenchService.UpdatesCheckInterval);1080this.autoUpdateDelayer = new ThrottledDelayer<void>(1000);1081this._register(toDisposable(() => {1082this.updatesCheckDelayer.cancel();1083this.autoUpdateDelayer.cancel();1084}));10851086urlService.registerHandler(this);10871088this.whenInitialized = this.initialize();1089}10901091private async initialize(): Promise<void> {1092// initialize local extensions1093await Promise.all([this.queryLocal(), this.extensionService.whenInstalledExtensionsRegistered()]);1094if (this._store.isDisposed) {1095return;1096}1097this.onDidChangeRunningExtensions(this.extensionService.extensions, []);1098this._register(this.extensionService.onDidChangeExtensions(({ added, removed }) => this.onDidChangeRunningExtensions(added, removed)));10991100await this.lifecycleService.when(LifecyclePhase.Eventually);1101if (this._store.isDisposed) {1102return;1103}11041105this.initializeAutoUpdate();1106this.updateExtensionsNotificaiton();1107this.reportInstalledExtensionsTelemetry();1108this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, this._store)(e => this.onDidDismissedNotificationsValueChange()));1109this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange()));1110this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_DONOT_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange()));1111this._register(Event.debounce(this.onChange, () => undefined, 100)(() => {1112this.updateExtensionsNotificaiton();1113this.reportProgressFromOtherSources();1114}));1115}11161117private initializeAutoUpdate(): void {1118// Register listeners for auto updates1119this._register(this.configurationService.onDidChangeConfiguration(e => {1120if (e.affectsConfiguration(AutoUpdateConfigurationKey)) {1121if (this.isAutoUpdateEnabled()) {1122this.eventuallyAutoUpdateExtensions();1123}1124}1125if (e.affectsConfiguration(AutoCheckUpdatesConfigurationKey)) {1126if (this.isAutoCheckUpdatesEnabled()) {1127this.checkForUpdates(`Enabled auto check updates`);1128}1129}1130}));1131this._register(this.extensionEnablementService.onEnablementChanged(platformExtensions => {1132if (this.isAutoCheckUpdatesEnabled() && this.getAutoUpdateValue() === 'onlyEnabledExtensions' && platformExtensions.some(e => this.extensionEnablementService.isEnabled(e))) {1133this.checkForUpdates('Extension enablement changed');1134}1135}));1136this._register(Event.debounce(this.onChange, () => undefined, 100)(() => this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0)));1137this._register(this.updateService.onStateChange(e => {1138if ((e.type === StateType.CheckingForUpdates && e.explicit) || e.type === StateType.AvailableForDownload || e.type === StateType.Downloaded) {1139this.telemetryService.publicLog2<{}, {1140owner: 'sandy081';1141comment: 'Report when update check is triggered on product update';1142}>('extensions:updatecheckonproductupdate');1143if (this.isAutoCheckUpdatesEnabled()) {1144this.checkForUpdates('Product update');1145}1146}1147}));11481149this._register(this.allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => {1150if (this.isAutoCheckUpdatesEnabled()) {1151this.checkForUpdates('Allowed extensions changed');1152}1153}));11541155this._register(this.meteredConnectionService.onDidChangeIsConnectionMetered(() => {1156if (this.isAutoCheckUpdatesEnabled()) {1157this.checkForUpdates('Connection is no longer metered');1158}1159if (isWeb && !this.isAutoUpdateEnabled()) {1160this.autoUpdateBuiltinExtensions();1161}1162}));11631164// Update AutoUpdate Contexts1165this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0);11661167// Check for updates1168this.eventuallyCheckForUpdates(true);11691170if (isWeb) {1171this.syncPinnedBuiltinExtensions();1172// Always auto update builtin extensions in web1173if (!this.isAutoUpdateEnabled()) {1174this.autoUpdateBuiltinExtensions();1175}1176}11771178this.registerAutoRestartListener();1179this._register(this.configurationService.onDidChangeConfiguration(e => {1180if (e.affectsConfiguration(AutoRestartConfigurationKey)) {1181this.registerAutoRestartListener();1182}1183}));1184}11851186private isAutoUpdateEnabled(): boolean {1187if (this.meteredConnectionService.isConnectionMetered) {1188return false;1189}1190return this.getAutoUpdateValue() !== false;1191}11921193getAutoUpdateValue(): AutoUpdateConfigurationValue {1194const autoUpdate = this.configurationService.getValue<AutoUpdateConfigurationValue>(AutoUpdateConfigurationKey);1195// eslint-disable-next-line local/code-no-any-casts1196if (<any>autoUpdate === 'onlySelectedExtensions') {1197return false;1198}1199return isBoolean(autoUpdate) || autoUpdate === 'onlyEnabledExtensions' ? autoUpdate : true;1200}12011202async updateAutoUpdateForAllExtensions(isAutoUpdateEnabled: boolean): Promise<void> {1203const wasAutoUpdateEnabled = this.isAutoUpdateEnabled();1204if (wasAutoUpdateEnabled === isAutoUpdateEnabled) {1205return;1206}12071208const result = await this.dialogService.confirm({1209title: nls.localize('confirmEnableDisableAutoUpdate', "Auto Update Extensions"),1210message: isAutoUpdateEnabled1211? nls.localize('confirmEnableAutoUpdate', "Do you want to enable auto update for all extensions?")1212: nls.localize('confirmDisableAutoUpdate', "Do you want to disable auto update for all extensions?"),1213detail: nls.localize('confirmEnableDisableAutoUpdateDetail', "This will reset any auto update settings you have set for individual extensions."),1214});1215if (!result.confirmed) {1216return;1217}12181219// Reset extensions enabled for auto update first to prevent them from being updated1220this.setEnabledAutoUpdateExtensions([]);12211222await this.configurationService.updateValue(AutoUpdateConfigurationKey, isAutoUpdateEnabled);12231224this.setDisabledAutoUpdateExtensions([]);1225await this.updateExtensionsPinnedState(!isAutoUpdateEnabled);1226this._onChange.fire(undefined);1227}12281229private readonly autoRestartListenerDisposable = this._register(new MutableDisposable());1230private registerAutoRestartListener(): void {1231this.autoRestartListenerDisposable.value = undefined;1232if (this.configurationService.getValue(AutoRestartConfigurationKey) === true) {1233this.autoRestartListenerDisposable.value = this.hostService.onDidChangeFocus(focus => {1234if (!focus && this.configurationService.getValue(AutoRestartConfigurationKey) === true) {1235this.updateRunningExtensions(undefined, true);1236}1237});1238}1239}12401241private reportInstalledExtensionsTelemetry() {1242const extensionIds = this.installed.filter(extension =>1243!extension.isBuiltin &&1244(extension.enablementState === EnablementState.EnabledWorkspace ||1245extension.enablementState === EnablementState.EnabledGlobally))1246.map(extension => ExtensionIdentifier.toKey(extension.identifier.id));1247this.telemetryService.publicLog2<InstalledExtensionsEvent, ExtensionsLoadClassification>('installedExtensions', { extensionIds: new TelemetryTrustedValue(extensionIds.join(';')), count: extensionIds.length });1248}12491250private async onDidChangeRunningExtensions(added: ReadonlyArray<IExtensionDescription>, removed: ReadonlyArray<IExtensionDescription>): Promise<void> {1251const changedExtensions: IExtension[] = [];1252const extensionsToFetch: IExtensionDescription[] = [];1253for (const desc of added) {1254const extension = this.installed.find(e => areSameExtensions({ id: desc.identifier.value, uuid: desc.uuid }, e.identifier));1255if (extension) {1256changedExtensions.push(extension);1257} else {1258extensionsToFetch.push(desc);1259}1260}1261const workspaceExtensions: IExtensionDescription[] = [];1262for (const desc of removed) {1263if (this.workspaceContextService.isInsideWorkspace(desc.extensionLocation)) {1264workspaceExtensions.push(desc);1265} else {1266extensionsToFetch.push(desc);1267}1268}1269if (extensionsToFetch.length) {1270const extensions = await this.getExtensions(extensionsToFetch.map(e => ({ id: e.identifier.value, uuid: e.uuid })), CancellationToken.None);1271changedExtensions.push(...extensions);1272}1273if (workspaceExtensions.length) {1274const extensions = await this.getResourceExtensions(workspaceExtensions.map(e => e.extensionLocation), true);1275changedExtensions.push(...extensions);1276}1277for (const changedExtension of changedExtensions) {1278this._onChange.fire(changedExtension);1279}1280}12811282private updateExtensionsPinnedState(pinned: boolean): Promise<void> {1283return this.progressService.withProgress({1284location: ProgressLocation.Extensions,1285title: nls.localize('updatingExtensions', "Updating Extensions Auto Update State"),1286}, () => this.extensionManagementService.resetPinnedStateForAllUserExtensions(pinned));1287}12881289private reset(): void {1290for (const task of this.tasksInProgress) {1291task.cancel();1292}1293this.tasksInProgress = [];1294this.installing = [];1295this.onDidChangeExtensions();1296this._onReset.fire();1297}12981299private onDidChangeExtensions(extension?: IExtension): void {1300this._installed = undefined;1301this._local = undefined;1302this._onChange.fire(extension);1303}13041305private _local: IExtension[] | undefined;1306get local(): IExtension[] {1307if (!this._local) {1308if (this.extensionsServers.length === 1) {1309this._local = this.installed;1310} else {1311this._local = [];1312const byId = groupByExtension(this.installed, r => r.identifier);1313for (const extensions of byId) {1314this._local.push(this.getPrimaryExtension(extensions));1315}1316}1317}1318return this._local;1319}13201321private _installed: IExtension[] | undefined;1322get installed(): IExtension[] {1323if (!this._installed) {1324this._installed = [];1325for (const extensions of this.extensionsServers) {1326for (const extension of extensions.local) {1327this._installed.push(extension);1328}1329}1330}1331return this._installed;1332}13331334get outdated(): IExtension[] {1335return this.installed.filter(e => e.outdated && e.local && e.state === ExtensionState.Installed);1336}13371338async queryLocal(server?: IExtensionManagementServer): Promise<IExtension[]> {1339if (server) {1340if (this.localExtensions && this.extensionManagementServerService.localExtensionManagementServer === server) {1341return this.localExtensions.queryInstalled(this.getProductVersion());1342}1343if (this.remoteExtensions && this.extensionManagementServerService.remoteExtensionManagementServer === server) {1344return this.remoteExtensions.queryInstalled(this.getProductVersion());1345}1346if (this.webExtensions && this.extensionManagementServerService.webExtensionManagementServer === server) {1347return this.webExtensions.queryInstalled(this.getProductVersion());1348}1349}13501351if (this.localExtensions) {1352try {1353await this.localExtensions.queryInstalled(this.getProductVersion());1354}1355catch (error) {1356this.logService.error(error);1357}1358}1359if (this.remoteExtensions) {1360try {1361await this.remoteExtensions.queryInstalled(this.getProductVersion());1362}1363catch (error) {1364this.logService.error(error);1365}1366}1367if (this.webExtensions) {1368try {1369await this.webExtensions.queryInstalled(this.getProductVersion());1370}1371catch (error) {1372this.logService.error(error);1373}1374}1375return this.local;1376}13771378queryGallery(token: CancellationToken): Promise<IPager<IExtension>>;1379queryGallery(options: IQueryOptions, token: CancellationToken): Promise<IPager<IExtension>>;1380async queryGallery(arg1: any, arg2?: any): Promise<IPager<IExtension>> {1381if (!this.galleryService.isEnabled()) {1382return singlePagePager([]);1383}13841385const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1;1386const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2;1387options.text = options.text ? this.resolveQueryText(options.text) : options.text;1388options.includePreRelease = isUndefined(options.includePreRelease) ? this.extensionManagementService.preferPreReleases : options.includePreRelease;13891390const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();1391const pager = await this.galleryService.query(options, token);1392this.syncInstalledExtensionsWithGallery(pager.firstPage);1393return {1394firstPage: pager.firstPage.map(gallery => this.fromGallery(gallery, extensionsControlManifest)),1395total: pager.total,1396pageSize: pager.pageSize,1397getPage: async (pageIndex, token) => {1398const page = await pager.getPage(pageIndex, token);1399this.syncInstalledExtensionsWithGallery(page);1400return page.map(gallery => this.fromGallery(gallery, extensionsControlManifest));1401}1402};1403}14041405getExtensions(extensionInfos: IExtensionInfo[], token: CancellationToken): Promise<IExtension[]>;1406getExtensions(extensionInfos: IExtensionInfo[], options: IExtensionQueryOptions, token: CancellationToken): Promise<IExtension[]>;1407async getExtensions(extensionInfos: IExtensionInfo[], arg1: any, arg2?: any): Promise<IExtension[]> {1408if (!this.galleryService.isEnabled()) {1409return [];1410}14111412extensionInfos.forEach(e => e.preRelease = e.preRelease ?? this.extensionManagementService.preferPreReleases);1413const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();1414const galleryExtensions = await this.galleryService.getExtensions(extensionInfos, arg1, arg2);1415this.syncInstalledExtensionsWithGallery(galleryExtensions);1416return galleryExtensions.map(gallery => this.fromGallery(gallery, extensionsControlManifest));1417}14181419async getResourceExtensions(locations: URI[], isWorkspaceScoped: boolean): Promise<IExtension[]> {1420const resourceExtensions = await this.extensionManagementService.getExtensions(locations);1421return resourceExtensions.map(resourceExtension => this.getInstalledExtensionMatchingLocation(resourceExtension.location)1422?? this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, undefined, { resourceExtension, isWorkspaceScoped }));1423}14241425private onDidDismissedNotificationsValueChange(): void {1426if (1427this.dismissedNotificationsValue !== this.getDismissedNotificationsValue() /* This checks if current window changed the value or not */1428) {1429this._dismissedNotificationsValue = undefined;1430this.updateExtensionsNotificaiton();1431}1432}14331434private updateExtensionsNotificaiton(): void {1435const computedNotificiations = this.computeExtensionsNotifications();1436const dismissedNotifications: string[] = [];14371438let extensionsNotification: IExtensionsNotification & { key: string } | undefined;1439if (computedNotificiations.length) {1440// populate dismissed notifications with the ones that are still valid1441for (const dismissedNotification of this.getDismissedNotifications()) {1442if (computedNotificiations.some(e => e.key === dismissedNotification)) {1443dismissedNotifications.push(dismissedNotification);1444}1445}1446if (!dismissedNotifications.includes(computedNotificiations[0].key)) {1447extensionsNotification = {1448message: computedNotificiations[0].message,1449severity: computedNotificiations[0].severity,1450extensions: computedNotificiations[0].extensions,1451key: computedNotificiations[0].key,1452dismiss: () => {1453this.setDismissedNotifications([...this.getDismissedNotifications(), computedNotificiations[0].key]);1454this.updateExtensionsNotificaiton();1455},1456};1457}1458}1459this.setDismissedNotifications(dismissedNotifications);14601461if (this.extensionsNotification?.key !== extensionsNotification?.key) {1462this.extensionsNotification = extensionsNotification;1463this._onDidChangeExtensionsNotification.fire(this.extensionsNotification);1464}1465}14661467private computeExtensionsNotifications(): Array<Omit<IExtensionsNotification, 'dismiss'> & { key: string }> {1468const computedNotificiations: Array<Omit<IExtensionsNotification, 'dismiss'> & { key: string }> = [];14691470const disallowedExtensions = this.local.filter(e => e.enablementState === EnablementState.DisabledByAllowlist);1471if (disallowedExtensions.length) {1472computedNotificiations.push({1473message: this.configurationService.inspect(AllowedExtensionsConfigKey).policy1474? nls.localize('disallowed extensions by policy', "Some extensions are disabled because they are not allowed by your system administrator.")1475: nls.localize('disallowed extensions', "Some extensions are disabled because they are configured not to be allowed."),1476severity: Severity.Warning,1477extensions: disallowedExtensions,1478key: 'disallowedExtensions:' + disallowedExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => e.identifier.id.toLowerCase()).join('-'),1479});1480}14811482const invalidExtensions = this.local.filter(e => e.enablementState === EnablementState.DisabledByInvalidExtension && !e.isWorkspaceScoped);1483if (invalidExtensions.length) {1484if (invalidExtensions.some(e => e.local && e.local.manifest.engines?.vscode &&1485(!isEngineValid(e.local.manifest.engines.vscode, this.productService.version, this.productService.date) || areApiProposalsCompatible([...e.local.manifest.enabledApiProposals ?? []]))1486)) {1487computedNotificiations.push({1488message: nls.localize('incompatibleExtensions', "Some extensions are disabled due to version incompatibility. Review and update them."),1489severity: Severity.Warning,1490extensions: invalidExtensions,1491key: 'incompatibleExtensions:' + invalidExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => `${e.identifier.id.toLowerCase()}@${e.local?.manifest.version}`).join('-'),1492});1493} else {1494computedNotificiations.push({1495message: nls.localize('invalidExtensions', "Invalid extensions detected. Review them."),1496severity: Severity.Warning,1497extensions: invalidExtensions,1498key: 'invalidExtensions:' + invalidExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => `${e.identifier.id.toLowerCase()}@${e.local?.manifest.version}`).join('-'),1499});1500}1501}15021503const deprecatedExtensions = this.local.filter(e => !!e.deprecationInfo && e.local && this.extensionEnablementService.isEnabled(e.local));1504if (deprecatedExtensions.length) {1505computedNotificiations.push({1506message: nls.localize('deprecated extensions', "Deprecated extensions detected. Review them and migrate to alternatives."),1507severity: Severity.Warning,1508extensions: deprecatedExtensions,1509key: 'deprecatedExtensions:' + deprecatedExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => e.identifier.id.toLowerCase()).join('-'),1510});1511}15121513return computedNotificiations;1514}15151516getExtensionsNotification(): IExtensionsNotification | undefined {1517return this.extensionsNotification;1518}15191520private resolveQueryText(text: string): string {1521text = text.replace(/@web/g, `tag:"${WEB_EXTENSION_TAG}"`);15221523const extensionRegex = /\bext:([^\s]+)\b/g;1524if (extensionRegex.test(text)) {1525text = text.replace(extensionRegex, (m, ext) => {15261527// Get curated keywords1528const lookup = this.productService.extensionKeywords || {};1529const keywords = lookup[ext] || [];15301531// Get mode name1532const languageId = this.languageService.guessLanguageIdByFilepathOrFirstLine(URI.file(`.${ext}`));1533const languageName = languageId && this.languageService.getLanguageName(languageId);1534const languageTag = languageName ? ` tag:"${languageName}"` : '';15351536// Construct a rich query1537return `tag:"__ext_${ext}" tag:"__ext_.${ext}" ${keywords.map(tag => `tag:"${tag}"`).join(' ')}${languageTag} tag:"${ext}"`;1538});1539}1540return text.substr(0, 350);1541}15421543private fromGallery(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): IExtension {1544let extension = this.getInstalledExtensionMatchingGallery(gallery);1545if (!extension) {1546extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined);1547(<Extension>extension).setExtensionsControlManifest(extensionsControlManifest);1548}1549return extension;1550}15511552private getInstalledExtensionMatchingGallery(gallery: IGalleryExtension): IExtension | null {1553for (const installed of this.local) {1554if (installed.identifier.uuid) { // Installed from Gallery1555if (installed.identifier.uuid === gallery.identifier.uuid) {1556return installed;1557}1558} else if (installed.local?.source !== 'resource') {1559if (areSameExtensions(installed.identifier, gallery.identifier)) { // Installed from other sources1560return installed;1561}1562}1563}1564return null;1565}15661567private getInstalledExtensionMatchingLocation(location: URI): IExtension | null {1568return this.local.find(e => e.local && this.uriIdentityService.extUri.isEqualOrParent(location, e.local?.location)) ?? null;1569}15701571async open(extension: IExtension | string, options?: IExtensionEditorOptions): Promise<void> {1572if (typeof extension === 'string') {1573const id = extension;1574extension = this.installed.find(e => areSameExtensions(e.identifier, { id })) ?? (await this.getExtensions([{ id: extension }], CancellationToken.None))[0];1575}1576if (!extension) {1577throw new Error(`Extension not found. ${extension}`);1578}1579await this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), options, options?.sideByside ? SIDE_GROUP : ACTIVE_GROUP);1580}15811582async openSearch(searchValue: string, preserveFoucs?: boolean): Promise<void> {1583const viewPaneContainer = (await this.viewsService.openViewContainer(VIEWLET_ID, true))?.getViewPaneContainer() as IExtensionsViewPaneContainer;1584viewPaneContainer.search(searchValue);1585if (!preserveFoucs) {1586viewPaneContainer.focus();1587}1588}15891590getExtensionRuntimeStatus(extension: IExtension): IExtensionRuntimeStatus | undefined {1591const extensionsStatus = this.extensionService.getExtensionsStatus();1592for (const id of Object.keys(extensionsStatus)) {1593if (areSameExtensions({ id }, extension.identifier)) {1594return extensionsStatus[id];1595}1596}1597return undefined;1598}15991600async updateRunningExtensions(message = nls.localize('restart', "Changing extension enablement"), auto: boolean = false): Promise<void> {1601const toAdd: ILocalExtension[] = [];1602const toRemove: string[] = [];16031604const extensionsToCheck = [...this.local];1605for (const extension of extensionsToCheck) {1606const runtimeState = extension.runtimeState;1607if (!runtimeState || runtimeState.action !== ExtensionRuntimeActionType.RestartExtensions) {1608continue;1609}1610if (extension.state === ExtensionState.Uninstalled) {1611toRemove.push(extension.identifier.id);1612continue;1613}1614if (!extension.local) {1615continue;1616}1617const isEnabled = this.extensionEnablementService.isEnabled(extension.local);1618if (isEnabled) {1619const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier));1620if (runningExtension) {1621toRemove.push(runningExtension.identifier.value);1622}1623toAdd.push(extension.local);1624} else {1625toRemove.push(extension.identifier.id);1626}1627}16281629for (const extension of this.extensionService.extensions) {1630if (extension.isUnderDevelopment) {1631continue;1632}1633if (extensionsToCheck.some(e => areSameExtensions({ id: extension.identifier.value, uuid: extension.uuid }, e.local?.identifier ?? e.identifier))) {1634continue;1635}1636// Extension is running but doesn't exist locally. Remove it from running extensions.1637toRemove.push(extension.identifier.value);1638}16391640if (toAdd.length || toRemove.length) {1641if (await this.extensionService.stopExtensionHosts(message, auto)) {1642await this.extensionService.startExtensionHosts({ toAdd, toRemove });1643if (auto) {1644this.notificationService.notify({1645severity: Severity.Info,1646message: nls.localize('extensionsAutoRestart', "Extensions were auto restarted to enable updates."),1647priority: NotificationPriority.SILENT1648});1649}1650type ExtensionsAutoRestartClassification = {1651owner: 'sandy081';1652comment: 'Report when extensions are auto restarted';1653count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions auto restarted' };1654auto: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the restart was triggered automatically' };1655};1656type ExtensionsAutoRestartEvent = {1657count: number;1658auto: boolean;1659};1660this.telemetryService.publicLog2<ExtensionsAutoRestartEvent, ExtensionsAutoRestartClassification>('extensions:autorestart', { count: toAdd.length + toRemove.length, auto });1661}1662}1663}16641665private getRuntimeState(extension: IExtension): ExtensionRuntimeState | undefined {1666const isUninstalled = extension.state === ExtensionState.Uninstalled;1667const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value }, extension.identifier));1668const reloadAction = this.extensionManagementServerService.remoteExtensionManagementServer ? ExtensionRuntimeActionType.ReloadWindow : ExtensionRuntimeActionType.RestartExtensions;1669const reloadActionLabel = reloadAction === ExtensionRuntimeActionType.ReloadWindow ? nls.localize('reload', "reload window") : nls.localize('restart extensions', "restart extensions");16701671if (isUninstalled) {1672const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension);1673const isSameExtensionRunning = runningExtension1674&& (!extension.server || extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)))1675&& (!extension.resourceExtension || this.uriIdentityService.extUri.isEqual(extension.resourceExtension.location, runningExtension.extensionLocation));1676if (!canRemoveRunningExtension && isSameExtensionRunning && !runningExtension.isUnderDevelopment) {1677return { action: reloadAction, reason: nls.localize('postUninstallTooltip', "Please {0} to complete the uninstallation of this extension.", reloadActionLabel) };1678}1679return undefined;1680}1681if (extension.local) {1682const isSameExtensionRunning = runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension));1683const isEnabled = this.extensionEnablementService.isEnabled(extension.local);16841685// Extension is running1686if (runningExtension) {1687if (isEnabled) {1688// No Reload is required if extension can run without reload1689if (this.extensionService.canAddExtension(toExtensionDescription(extension.local))) {1690return undefined;1691}1692const runningExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension));16931694if (isSameExtensionRunning) {1695// Different version or target platform of same extension is running. Requires reload to run the current version1696if (!runningExtension.isUnderDevelopment && (extension.version !== runningExtension.version || extension.local.targetPlatform !== runningExtension.targetPlatform)) {1697const productCurrentVersion = this.getProductCurrentVersion();1698const productUpdateVersion = this.getProductUpdateVersion();1699if (productUpdateVersion1700&& !isEngineValid(extension.local.manifest.engines.vscode, productCurrentVersion.version, productCurrentVersion.date)1701&& isEngineValid(extension.local.manifest.engines.vscode, productUpdateVersion.version, productUpdateVersion.date)1702) {1703const state = this.updateService.state;1704if (state.type === StateType.AvailableForDownload) {1705return { action: ExtensionRuntimeActionType.DownloadUpdate, reason: nls.localize('postUpdateDownloadTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) };1706}1707if (state.type === StateType.Downloaded) {1708return { action: ExtensionRuntimeActionType.ApplyUpdate, reason: nls.localize('postUpdateUpdateTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) };1709}1710if (state.type === StateType.Ready) {1711return { action: ExtensionRuntimeActionType.QuitAndInstall, reason: nls.localize('postUpdateRestartTooltip', "Please restart {0} to enable the updated extension.", this.productService.nameLong) };1712}1713return undefined;1714}1715return { action: reloadAction, reason: nls.localize('postUpdateTooltip', "Please {0} to enable the updated extension.", reloadActionLabel) };1716}17171718if (this.extensionsServers.length > 1) {1719const extensionInOtherServer = this.installed.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server !== extension.server)[0];1720if (extensionInOtherServer) {1721// This extension prefers to run on UI/Local side but is running in remote1722if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.localExtensionManagementServer) {1723return { action: reloadAction, reason: nls.localize('enable locally', "Please {0} to enable this extension locally.", reloadActionLabel) };1724}17251726// This extension prefers to run on Workspace/Remote side but is running in local1727if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.remoteExtensionManagementServer) {1728return { action: reloadAction, reason: nls.localize('enable remote', "Please {0} to enable this extension in {1}.", reloadActionLabel, this.extensionManagementServerService.remoteExtensionManagementServer?.label) };1729}1730}1731}17321733} else {17341735if (extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) {1736// This extension prefers to run on UI/Local side but is running in remote1737if (this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest)) {1738return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };1739}1740}1741if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) {1742// This extension prefers to run on Workspace/Remote side but is running in local1743if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest)) {1744return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };1745}1746}1747}1748return undefined;1749} else {1750if (isSameExtensionRunning) {1751return { action: reloadAction, reason: nls.localize('postDisableTooltip', "Please {0} to disable this extension.", reloadActionLabel) };1752}1753}1754return undefined;1755}17561757// Extension is not running1758else {1759if (isEnabled && !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) {1760return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };1761}17621763const otherServer = extension.server ? extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null;1764if (otherServer && extension.enablementState === EnablementState.DisabledByExtensionKind) {1765const extensionInOtherServer = this.local.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server === otherServer)[0];1766// Same extension in other server exists and1767if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) {1768return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };1769}1770}1771}1772}1773return undefined;1774}17751776private getPrimaryExtension(extensions: IExtension[]): IExtension {1777if (extensions.length === 1) {1778return extensions[0];1779}17801781const enabledExtensions = extensions.filter(e => e.local && this.extensionEnablementService.isEnabled(e.local));1782if (enabledExtensions.length === 1) {1783return enabledExtensions[0];1784}17851786const extensionsToChoose = enabledExtensions.length ? enabledExtensions : extensions;1787const manifest = extensionsToChoose.find(e => e.local && e.local.manifest)?.local?.manifest;17881789// Manifest is not found which should not happen.1790// In which case return the first extension.1791if (!manifest) {1792return extensionsToChoose[0];1793}17941795const extensionKinds = this.extensionManifestPropertiesService.getExtensionKind(manifest);17961797let extension = extensionsToChoose.find(extension => {1798for (const extensionKind of extensionKinds) {1799switch (extensionKind) {1800case 'ui':1801/* UI extension is chosen only if it is installed locally */1802if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {1803return true;1804}1805return false;1806case 'workspace':1807/* Choose remote workspace extension if exists */1808if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) {1809return true;1810}1811return false;1812case 'web':1813/* Choose web extension if exists */1814if (extension.server === this.extensionManagementServerService.webExtensionManagementServer) {1815return true;1816}1817return false;1818}1819}1820return false;1821});18221823if (!extension && this.extensionManagementServerService.localExtensionManagementServer) {1824extension = extensionsToChoose.find(extension => {1825for (const extensionKind of extensionKinds) {1826switch (extensionKind) {1827case 'workspace':1828/* Choose local workspace extension if exists */1829if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {1830return true;1831}1832return false;1833case 'web':1834/* Choose local web extension if exists */1835if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {1836return true;1837}1838return false;1839}1840}1841return false;1842});1843}18441845if (!extension && this.extensionManagementServerService.webExtensionManagementServer) {1846extension = extensionsToChoose.find(extension => {1847for (const extensionKind of extensionKinds) {1848switch (extensionKind) {1849case 'web':1850/* Choose web extension if exists */1851if (extension.server === this.extensionManagementServerService.webExtensionManagementServer) {1852return true;1853}1854return false;1855}1856}1857return false;1858});1859}18601861if (!extension && this.extensionManagementServerService.remoteExtensionManagementServer) {1862extension = extensionsToChoose.find(extension => {1863for (const extensionKind of extensionKinds) {1864switch (extensionKind) {1865case 'web':1866/* Choose remote web extension if exists */1867if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) {1868return true;1869}1870return false;1871}1872}1873return false;1874});1875}18761877return extension || extensions[0];1878}18791880private getExtensionState(extension: Extension): ExtensionState {1881if (this.installing.some(i => areSameExtensions(i.identifier, extension.identifier) && (!extension.server || i.server === extension.server))) {1882return ExtensionState.Installing;1883}1884if (this.remoteExtensions) {1885const state = this.remoteExtensions.getExtensionState(extension);1886if (state !== ExtensionState.Uninstalled) {1887return state;1888}1889}1890if (this.webExtensions) {1891const state = this.webExtensions.getExtensionState(extension);1892if (state !== ExtensionState.Uninstalled) {1893return state;1894}1895}1896if (this.localExtensions) {1897return this.localExtensions.getExtensionState(extension);1898}1899return ExtensionState.Uninstalled;1900}19011902async checkForUpdates(reason?: string, onlyBuiltin?: boolean): Promise<void> {1903if (reason) {1904this.logService.trace(`[Extensions]: Checking for updates. Reason: ${reason}`);1905} else {1906this.logService.trace(`[Extensions]: Checking for updates`);1907}1908if (!this.galleryService.isEnabled()) {1909return;1910}1911const extensions: Extensions[] = [];1912if (this.localExtensions) {1913extensions.push(this.localExtensions);1914}1915if (this.remoteExtensions) {1916extensions.push(this.remoteExtensions);1917}1918if (this.webExtensions) {1919extensions.push(this.webExtensions);1920}1921if (!extensions.length) {1922return;1923}1924const infos: IExtensionInfo[] = [];1925for (const installed of this.local) {1926if (onlyBuiltin && !installed.isBuiltin) {1927// Skip if check updates only for builtin extensions and current extension is not builtin.1928continue;1929}1930if (installed.isBuiltin && !installed.local?.pinned && (installed.type === ExtensionType.System || !installed.local?.identifier.uuid)) {1931// Skip checking updates for a builtin extension if it is a system extension or if it does not has Marketplace identifier1932continue;1933}1934if (installed.local?.source === 'resource') {1935continue;1936}1937infos.push({ ...installed.identifier, preRelease: !!installed.local?.preRelease });1938}1939if (infos.length) {1940const targetPlatform = await extensions[0].server.extensionManagementService.getTargetPlatform();1941type GalleryServiceUpdatesCheckClassification = {1942owner: 'sandy081';1943comment: 'Report when a request is made to check for updates of extensions';1944count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to check update' };1945};1946type GalleryServiceUpdatesCheckEvent = {1947count: number;1948};1949this.telemetryService.publicLog2<GalleryServiceUpdatesCheckEvent, GalleryServiceUpdatesCheckClassification>('galleryService:checkingForUpdates', {1950count: infos.length,1951});1952this.logService.trace(`Checking updates for extensions`, infos.map(e => e.id).join(', '));1953const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion() }, CancellationToken.None);1954if (galleryExtensions.length) {1955await this.syncInstalledExtensionsWithGallery(galleryExtensions, infos);1956}1957}1958}19591960async updateAll(): Promise<InstallExtensionResult[]> {1961const toUpdate: InstallExtensionInfo[] = [];1962this.outdated.forEach((extension) => {1963if (extension.gallery) {1964toUpdate.push({1965extension: extension.gallery,1966options: {1967operation: InstallOperation.Update,1968installPreReleaseVersion: extension.local?.isPreReleaseVersion,1969profileLocation: this.userDataProfileService.currentProfile.extensionsResource,1970isApplicationScoped: extension.local?.isApplicationScoped,1971context: { [EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT]: true }1972}1973});1974}1975});1976return this.extensionManagementService.installGalleryExtensions(toUpdate);1977}19781979async downloadVSIX(extensionId: string, versionKind: 'prerelease' | 'release' | 'any'): Promise<void> {1980let version: IGalleryExtensionVersion | undefined;1981if (versionKind === 'any') {1982version = await this.pickVersionToDownload(extensionId);1983if (!version) {1984return;1985}1986}19871988const extensionInfo = version ? { id: extensionId, version: version.version } : { id: extensionId, preRelease: versionKind === 'prerelease' };1989const queryOptions: IExtensionQueryOptions = version ? {} : { compatible: true };19901991let [galleryExtension] = await this.galleryService.getExtensions([extensionInfo], queryOptions, CancellationToken.None);1992if (!galleryExtension) {1993throw new Error(nls.localize('extension not found', "Extension '{0}' not found.", extensionId));1994}19951996let targetPlatform = galleryExtension.properties.targetPlatform;1997const options = [];1998for (const targetPlatform of version?.targetPlatforms ?? galleryExtension.allTargetPlatforms) {1999if (targetPlatform !== TargetPlatform.UNKNOWN && targetPlatform !== TargetPlatform.UNIVERSAL) {2000options.push({2001label: targetPlatform === TargetPlatform.UNDEFINED ? nls.localize('allplatforms', "All Platforms") : TargetPlatformToString(targetPlatform),2002id: targetPlatform2003});2004}2005}2006if (options.length > 1) {2007const message = nls.localize('platform placeholder', "Please select the platform for which you want to download the VSIX");2008const option = await this.quickInputService.pick(options.sort((a, b) => a.label.localeCompare(b.label)), { placeHolder: message });2009if (!option) {2010return;2011}2012targetPlatform = option.id;2013}20142015if (targetPlatform !== galleryExtension.properties.targetPlatform) {2016[galleryExtension] = await this.galleryService.getExtensions([extensionInfo], { ...queryOptions, targetPlatform }, CancellationToken.None);2017}20182019const result = await this.fileDialogService.showOpenDialog({2020title: nls.localize('download title', "Select folder to download the VSIX"),2021canSelectFiles: false,2022canSelectFolders: true,2023canSelectMany: false,2024openLabel: nls.localize('download', "Download"),2025});20262027if (!result?.[0]) {2028return;2029}20302031this.progressService.withProgress({ location: ProgressLocation.Notification }, async progress => {2032try {2033progress.report({ message: nls.localize('downloading...', "Downloading VSIX...") });2034const name = `${galleryExtension.identifier.id}-${galleryExtension.version}${targetPlatform !== TargetPlatform.UNDEFINED && targetPlatform !== TargetPlatform.UNIVERSAL && targetPlatform !== TargetPlatform.UNKNOWN ? `-${targetPlatform}` : ''}.vsix`;2035await this.galleryService.download(galleryExtension, this.uriIdentityService.extUri.joinPath(result[0], name), InstallOperation.None);2036this.notificationService.info(nls.localize('download.completed', "Successfully downloaded the VSIX"));2037} catch (error) {2038this.notificationService.error(nls.localize('download.failed', "Error while downloading the VSIX: {0}", getErrorMessage(error)));2039}2040});2041}20422043private async pickVersionToDownload(extensionId: string): Promise<IGalleryExtensionVersion | undefined> {2044const allVersions = await this.galleryService.getAllVersions({ id: extensionId });2045if (!allVersions.length) {2046await this.dialogService.info(nls.localize('no versions', "This extension has no other versions."));2047return;2048}20492050const picks = allVersions.map((v, i) => {2051return {2052id: v.version,2053label: v.version,2054description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${nls.localize('pre-release', "pre-release")})` : ''}`,2055ariaLabel: `${v.isPreReleaseVersion ? 'Pre-Release version' : 'Release version'} ${v.version}`,2056data: v,2057};2058});2059const pick = await this.quickInputService.pick(picks,2060{2061placeHolder: nls.localize('selectVersion', "Select Version to Download"),2062matchOnDetail: true2063});2064return pick?.data;2065}20662067private async syncInstalledExtensionsWithGallery(gallery: IGalleryExtension[], flagExtensionsMissingFromGallery?: IExtensionInfo[]): Promise<void> {2068const extensions: Extensions[] = [];2069if (this.localExtensions) {2070extensions.push(this.localExtensions);2071}2072if (this.remoteExtensions) {2073extensions.push(this.remoteExtensions);2074}2075if (this.webExtensions) {2076extensions.push(this.webExtensions);2077}2078if (!extensions.length) {2079return;2080}2081await Promise.allSettled(extensions.map(extensions => extensions.syncInstalledExtensionsWithGallery(gallery, this.getProductVersion(), flagExtensionsMissingFromGallery)));2082if (this.outdated.length) {2083this.logService.info(`Auto updating outdated extensions.`, this.outdated.map(e => e.identifier.id).join(', '));2084this.eventuallyAutoUpdateExtensions();2085}2086}20872088private isAutoCheckUpdatesEnabled(): boolean {2089if (this.meteredConnectionService.isConnectionMetered) {2090return false;2091}2092return this.configurationService.getValue(AutoCheckUpdatesConfigurationKey);2093}20942095private eventuallyCheckForUpdates(immediate = false): void {2096this.updatesCheckDelayer.cancel();2097this.updatesCheckDelayer.trigger(async () => {2098if (this.isAutoCheckUpdatesEnabled()) {2099await this.checkForUpdates();2100}2101this.eventuallyCheckForUpdates();2102}, immediate ? 0 : this.getUpdatesCheckInterval()).then(undefined, err => null);2103}21042105private getUpdatesCheckInterval(): number {2106if (this.productService.quality === 'insider' && this.getProductUpdateVersion()) {2107return 1000 * 60 * 60 * 1; // 1 hour2108}2109return ExtensionsWorkbenchService.UpdatesCheckInterval;2110}21112112private eventuallyAutoUpdateExtensions(): void {2113this.autoUpdateDelayer.trigger(() => this.autoUpdateExtensions())2114.then(undefined, err => null);2115}21162117private async autoUpdateBuiltinExtensions(): Promise<void> {2118if (this.meteredConnectionService.isConnectionMetered) {2119return;2120}2121await this.checkForUpdates(undefined, true);2122const toUpdate = this.outdated.filter(e => e.isBuiltin);2123await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true } : undefined)));2124}21252126private async syncPinnedBuiltinExtensions(): Promise<void> {2127const infos: IExtensionInfo[] = [];2128for (const installed of this.local) {2129if (installed.isBuiltin && installed.local?.pinned && installed.local?.identifier.uuid) {2130infos.push({ ...installed.identifier, version: installed.version });2131}2132}2133if (infos.length) {2134const galleryExtensions = await this.galleryService.getExtensions(infos, CancellationToken.None);2135if (galleryExtensions.length) {2136await this.syncInstalledExtensionsWithGallery(galleryExtensions);2137}2138}2139}21402141private async autoUpdateExtensions(): Promise<void> {2142if (this.meteredConnectionService.isConnectionMetered) {2143this.logService.trace('[Extensions]: Skipping auto-update because connection is metered');2144return;2145}21462147const toUpdate: IExtension[] = [];2148const disabledAutoUpdate = [];2149const consentRequired = [];2150for (const extension of this.outdated) {2151if (!this.shouldAutoUpdateExtension(extension)) {2152disabledAutoUpdate.push(extension.identifier.id);2153continue;2154}2155if (await this.shouldRequireConsentToUpdate(extension)) {2156consentRequired.push(extension.identifier.id);2157continue;2158}2159toUpdate.push(extension);2160}21612162if (disabledAutoUpdate.length) {2163this.logService.trace('Auto update disabled for extensions', disabledAutoUpdate.join(', '));2164}21652166if (consentRequired.length) {2167this.logService.info('Auto update consent required for extensions', consentRequired.join(', '));2168}21692170if (!toUpdate.length) {2171return;2172}21732174const productVersion = this.getProductVersion();2175await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true, productVersion } : { productVersion })));2176}21772178private getProductVersion(): IProductVersion {2179return this.getProductUpdateVersion() ?? this.getProductCurrentVersion();2180}21812182private getProductCurrentVersion(): IProductVersion {2183return { version: this.productService.version, date: this.productService.date };2184}21852186private getProductUpdateVersion(): IProductVersion | undefined {2187switch (this.updateService.state.type) {2188case StateType.AvailableForDownload:2189case StateType.Downloaded:2190case StateType.Updating:2191case StateType.Ready: {2192const version = this.updateService.state.update.productVersion;2193if (version && semver.valid(version)) {2194return { version, date: this.updateService.state.update.timestamp ? new Date(this.updateService.state.update.timestamp).toISOString() : undefined };2195}2196}2197}2198return undefined;2199}22002201private shouldAutoUpdateExtension(extension: IExtension): boolean {2202if (extension.deprecationInfo?.disallowInstall) {2203return false;2204}22052206const autoUpdateValue = this.getAutoUpdateValue();22072208if (autoUpdateValue === false) {2209const extensionsToAutoUpdate = this.getEnabledAutoUpdateExtensions();2210const extensionId = extension.identifier.id.toLowerCase();2211if (extensionsToAutoUpdate.includes(extensionId)) {2212return true;2213}2214if (this.isAutoUpdateEnabledForPublisher(extension.publisher) && !extensionsToAutoUpdate.includes(`-${extensionId}`)) {2215return true;2216}2217return false;2218}22192220if (extension.pinned) {2221return false;2222}22232224const disabledAutoUpdateExtensions = this.getDisabledAutoUpdateExtensions();2225if (disabledAutoUpdateExtensions.includes(extension.identifier.id.toLowerCase())) {2226return false;2227}22282229if (autoUpdateValue === true) {2230return true;2231}22322233if (autoUpdateValue === 'onlyEnabledExtensions') {2234return extension.enablementState !== EnablementState.DisabledGlobally && extension.enablementState !== EnablementState.DisabledWorkspace;2235}22362237return false;2238}22392240async shouldRequireConsentToUpdate(extension: IExtension): Promise<string | undefined> {2241if (!extension.outdated) {2242return;2243}22442245if (!extension.gallery || !extension.local) {2246return;2247}22482249if (extension.local.identifier.uuid && extension.local.identifier.uuid !== extension.gallery.identifier.uuid) {2250return nls.localize('consentRequiredToUpdateRepublishedExtension', "The marketplace metadata of this extension changed, likely due to a re-publish.");2251}22522253if (!extension.local.manifest.engines.vscode || extension.local.manifest.main || extension.local.manifest.browser) {2254return;2255}22562257if (isDefined(extension.gallery.properties?.executesCode)) {2258if (!extension.gallery.properties.executesCode) {2259return;2260}2261} else {2262const manifest = extension instanceof Extension2263? await extension.getGalleryManifest()2264: await this.galleryService.getManifest(extension.gallery, CancellationToken.None);2265if (!manifest?.main && !manifest?.browser) {2266return;2267}2268}22692270return nls.localize('consentRequiredToUpdate', "The update for {0} extension introduces executable code, which is not present in the currently installed version.", extension.displayName);2271}22722273isAutoUpdateEnabledFor(extensionOrPublisher: IExtension | string): boolean {2274if (isString(extensionOrPublisher)) {2275if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) {2276throw new Error('Expected publisher string, found extension identifier');2277}2278if (this.isAutoUpdateEnabled()) {2279return true;2280}2281return this.isAutoUpdateEnabledForPublisher(extensionOrPublisher);2282}2283return this.shouldAutoUpdateExtension(extensionOrPublisher);2284}22852286private isAutoUpdateEnabledForPublisher(publisher: string): boolean {2287const publishersToAutoUpdate = this.getPublishersToAutoUpdate();2288return publishersToAutoUpdate.includes(publisher.toLowerCase());2289}22902291async updateAutoUpdateEnablementFor(extensionOrPublisher: IExtension | string, enable: boolean): Promise<void> {2292if (this.isAutoUpdateEnabled()) {2293if (isString(extensionOrPublisher)) {2294throw new Error('Expected extension, found publisher string');2295}2296const disabledAutoUpdateExtensions = this.getDisabledAutoUpdateExtensions();2297const extensionId = extensionOrPublisher.identifier.id.toLowerCase();2298const extensionIndex = disabledAutoUpdateExtensions.indexOf(extensionId);2299if (enable) {2300if (extensionIndex !== -1) {2301disabledAutoUpdateExtensions.splice(extensionIndex, 1);2302}2303}2304else {2305if (extensionIndex === -1) {2306disabledAutoUpdateExtensions.push(extensionId);2307}2308}2309this.setDisabledAutoUpdateExtensions(disabledAutoUpdateExtensions);2310if (enable && extensionOrPublisher.local && extensionOrPublisher.pinned) {2311await this.extensionManagementService.updateMetadata(extensionOrPublisher.local, { pinned: false });2312}2313this._onChange.fire(extensionOrPublisher);2314}23152316else {2317const enabledAutoUpdateExtensions = this.getEnabledAutoUpdateExtensions();2318if (isString(extensionOrPublisher)) {2319if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) {2320throw new Error('Expected publisher string, found extension identifier');2321}2322extensionOrPublisher = extensionOrPublisher.toLowerCase();2323if (this.isAutoUpdateEnabledFor(extensionOrPublisher) !== enable) {2324if (enable) {2325enabledAutoUpdateExtensions.push(extensionOrPublisher);2326} else {2327if (enabledAutoUpdateExtensions.includes(extensionOrPublisher)) {2328enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionOrPublisher), 1);2329}2330}2331}2332this.setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions);2333for (const e of this.installed) {2334if (e.publisher.toLowerCase() === extensionOrPublisher) {2335this._onChange.fire(e);2336}2337}2338} else {2339const extensionId = extensionOrPublisher.identifier.id.toLowerCase();2340const enableAutoUpdatesForPublisher = this.isAutoUpdateEnabledFor(extensionOrPublisher.publisher.toLowerCase());2341const enableAutoUpdatesForExtension = enabledAutoUpdateExtensions.includes(extensionId);2342const disableAutoUpdatesForExtension = enabledAutoUpdateExtensions.includes(`-${extensionId}`);23432344if (enable) {2345if (disableAutoUpdatesForExtension) {2346enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(`-${extensionId}`), 1);2347}2348if (enableAutoUpdatesForPublisher) {2349if (enableAutoUpdatesForExtension) {2350enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionId), 1);2351}2352} else {2353if (!enableAutoUpdatesForExtension) {2354enabledAutoUpdateExtensions.push(extensionId);2355}2356}2357}2358// Disable Auto Updates2359else {2360if (enableAutoUpdatesForExtension) {2361enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionId), 1);2362}2363if (enableAutoUpdatesForPublisher) {2364if (!disableAutoUpdatesForExtension) {2365enabledAutoUpdateExtensions.push(`-${extensionId}`);2366}2367} else {2368if (disableAutoUpdatesForExtension) {2369enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(`-${extensionId}`), 1);2370}2371}2372}2373this.setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions);2374this._onChange.fire(extensionOrPublisher);2375}2376}23772378if (enable) {2379this.autoUpdateExtensions();2380}2381}23822383private onDidSelectedExtensionToAutoUpdateValueChange(): void {2384if (2385this.enabledAuotUpdateExtensionsValue !== this.getEnabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */2386|| this.disabledAutoUpdateExtensionsValue !== this.getDisabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */2387) {2388const userExtensions = this.installed.filter(e => !e.isBuiltin);2389const groupBy = (extensions: IExtension[]): IExtension[][] => {2390const shouldAutoUpdate: IExtension[] = [];2391const shouldNotAutoUpdate: IExtension[] = [];2392for (const extension of extensions) {2393if (this.shouldAutoUpdateExtension(extension)) {2394shouldAutoUpdate.push(extension);2395} else {2396shouldNotAutoUpdate.push(extension);2397}2398}2399return [shouldAutoUpdate, shouldNotAutoUpdate];2400};24012402const [wasShouldAutoUpdate, wasShouldNotAutoUpdate] = groupBy(userExtensions);2403this._enabledAutoUpdateExtensionsValue = undefined;2404this._disabledAutoUpdateExtensionsValue = undefined;2405const [shouldAutoUpdate, shouldNotAutoUpdate] = groupBy(userExtensions);24062407for (const e of wasShouldAutoUpdate ?? []) {2408if (shouldNotAutoUpdate?.includes(e)) {2409this._onChange.fire(e);2410}2411}2412for (const e of wasShouldNotAutoUpdate ?? []) {2413if (shouldAutoUpdate?.includes(e)) {2414this._onChange.fire(e);2415}2416}2417}2418}24192420async canInstall(extension: IExtension): Promise<true | IMarkdownString> {2421if (!(extension instanceof Extension)) {2422return new MarkdownString().appendText(nls.localize('not an extension', "The provided object is not an extension."));2423}24242425if (extension.isMalicious) {2426return new MarkdownString().appendText(nls.localize('malicious', "This extension is reported to be problematic."));2427}24282429if (extension.deprecationInfo?.disallowInstall) {2430return new MarkdownString().appendText(nls.localize('disallowed', "This extension is disallowed to be installed."));2431}24322433if (extension.gallery) {2434if (!extension.gallery.isSigned && shouldRequireRepositorySignatureFor(extension.private, await this.extensionGalleryManifestService.getExtensionGalleryManifest())) {2435return new MarkdownString().appendText(nls.localize('not signed', "This extension is not signed."));2436}24372438const localResult = this.localExtensions ? await this.localExtensions.canInstall(extension.gallery) : undefined;2439if (localResult === true) {2440return true;2441}24422443const remoteResult = this.remoteExtensions ? await this.remoteExtensions.canInstall(extension.gallery) : undefined;2444if (remoteResult === true) {2445return true;2446}24472448const webResult = this.webExtensions ? await this.webExtensions.canInstall(extension.gallery) : undefined;2449if (webResult === true) {2450return true;2451}24522453return localResult ?? remoteResult ?? webResult ?? new MarkdownString().appendText(nls.localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", extension.displayName ?? extension.identifier.id));2454}24552456if (extension.resourceExtension && await this.extensionManagementService.canInstall(extension.resourceExtension) === true) {2457return true;2458}24592460return new MarkdownString().appendText(nls.localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", extension.displayName ?? extension.identifier.id));2461}24622463async install(arg: string | URI | IExtension, installOptions: InstallExtensionOptions = {}, progressLocation?: ProgressLocation | string): Promise<IExtension> {2464let installable: URI | IGalleryExtension | IResourceExtension | undefined;2465let extension: IExtension | undefined;2466let servers: IExtensionManagementServer[] | undefined;24672468if (arg instanceof URI) {2469installable = arg;2470} else {2471let installableInfo: IExtensionInfo | undefined;2472let gallery: IGalleryExtension | undefined;24732474// Install by id2475if (isString(arg)) {2476extension = this.local.find(e => areSameExtensions(e.identifier, { id: arg }));2477if (!extension?.isBuiltin) {2478installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions.installPreReleaseVersion ?? this.extensionManagementService.preferPreReleases };2479}2480}2481// Install by gallery2482else if (arg.gallery) {2483extension = arg;2484gallery = arg.gallery;2485if (installOptions.version && installOptions.version !== gallery?.version) {2486installableInfo = { id: extension.identifier.id, version: installOptions.version };2487}2488}2489// Install by resource2490else if (arg.resourceExtension) {2491extension = arg;2492installable = arg.resourceExtension;2493}24942495if (installableInfo) {2496const targetPlatform = extension?.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined;2497gallery = (await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None)).at(0);2498}24992500if (!extension && gallery) {2501extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined);2502(<Extension>extension).setExtensionsControlManifest(await this.extensionManagementService.getExtensionsControlManifest());2503}25042505if (extension?.isMalicious) {2506throw new Error(nls.localize('malicious', "This extension is reported to be problematic."));2507}25082509if (gallery) {2510// If requested to install everywhere2511// then install the extension in all the servers where it is not installed2512if (installOptions.installEverywhere) {2513servers = [];2514const installableServers = await this.extensionManagementService.getInstallableServers(gallery);2515for (const extensionsServer of this.extensionsServers) {2516if (installableServers.includes(extensionsServer.server) && !extensionsServer.local.find(e => areSameExtensions(e.identifier, gallery.identifier))) {2517servers.push(extensionsServer.server);2518}2519}2520}2521// If requested to enable and extension is already installed2522// Check if the extension is disabled because of extension kind2523// If so, install the extension in the server that is compatible.2524else if (installOptions.enable && extension?.local) {2525servers = [];2526if (extension.enablementState === EnablementState.DisabledByExtensionKind) {2527const [installableServer] = await this.extensionManagementService.getInstallableServers(gallery);2528if (installableServer) {2529servers.push(installableServer);2530}2531}2532}2533}25342535if (!servers || servers.length) {2536if (!installable) {2537if (!gallery) {2538const id = isString(arg) ? arg : (<IExtension>arg).identifier.id;2539const manifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();2540const reportIssueUri = manifest ? getExtensionGalleryManifestResourceUri(manifest, ExtensionGalleryResourceType.ContactSupportUri) : undefined;2541const reportIssueMessage = reportIssueUri ? nls.localize('report issue', "If this issue persists, please report it at {0}", reportIssueUri.toString()) : '';2542if (installOptions.version) {2543const message = nls.localize('not found version', "The extension '{0}' cannot be installed because the requested version '{1}' was not found.", id, installOptions.version);2544throw new ExtensionManagementError(reportIssueMessage ? `${message} ${reportIssueMessage}` : message, ExtensionManagementErrorCode.NotFound);2545} else {2546const message = nls.localize('not found', "The extension '{0}' cannot be installed because it was not found.", id);2547throw new ExtensionManagementError(reportIssueMessage ? `${message} ${reportIssueMessage}` : message, ExtensionManagementErrorCode.NotFound);2548}2549}2550installable = gallery;2551}2552if (installOptions.version) {2553installOptions.installGivenVersion = true;2554}2555if (extension?.isWorkspaceScoped) {2556installOptions.isWorkspaceScoped = true;2557}2558}2559}25602561if (installable) {2562if (installOptions.justification) {2563const syncCheck = isUndefined(installOptions.isMachineScoped) && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions);2564const buttons: IPromptButton<boolean>[] = [];2565buttons.push({2566label: isString(installOptions.justification) || !installOptions.justification.action2567? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension")2568: nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), run: () => true2569});2570if (!extension) {2571buttons.push({ label: nls.localize('open', "Open Extension"), run: () => { this.open(extension!); return false; } });2572}2573const result = await this.dialogService.prompt<boolean>({2574title: nls.localize('installExtensionTitle', "Install Extension"),2575message: extension ? nls.localize('installExtensionMessage', "Would you like to install '{0}' extension from '{1}'?", extension.displayName, extension.publisherDisplayName) : nls.localize('installVSIXMessage', "Would you like to install the extension?"),2576detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason,2577cancelButton: true,2578buttons,2579checkbox: syncCheck ? {2580label: nls.localize('sync extension', "Sync this extension"),2581checked: true,2582} : undefined,2583});2584if (!result.result) {2585throw new CancellationError();2586}2587if (syncCheck) {2588installOptions.isMachineScoped = !result.checkboxChecked;2589}2590}2591if (installable instanceof URI) {2592extension = await this.doInstall(undefined, () => this.installFromVSIX(installable, installOptions), progressLocation);2593} else if (extension) {2594if (extension.resourceExtension) {2595extension = await this.doInstall(extension, () => this.extensionManagementService.installResourceExtension(installable as IResourceExtension, installOptions), progressLocation);2596} else {2597extension = await this.doInstall(extension, () => this.installFromGallery(extension!, installable as IGalleryExtension, installOptions, servers), progressLocation);2598}2599}2600}26012602if (!extension) {2603throw new Error(nls.localize('unknown', "Unable to install extension"));2604}26052606if (installOptions.enable) {2607if (extension.enablementState === EnablementState.DisabledWorkspace || extension.enablementState === EnablementState.DisabledGlobally) {2608if (installOptions.justification) {2609const result = await this.dialogService.confirm({2610title: nls.localize('enableExtensionTitle', "Enable Extension"),2611message: nls.localize('enableExtensionMessage', "Would you like to enable '{0}' extension?", extension.displayName),2612detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason,2613primaryButton: isString(installOptions.justification) ? nls.localize({ key: 'enableButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Enable Extension") : nls.localize({ key: 'enableButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Enable Extension and {0}", installOptions.justification.action),2614});2615if (!result.confirmed) {2616throw new CancellationError();2617}2618}2619await this.setEnablement(extension, extension.enablementState === EnablementState.DisabledWorkspace ? EnablementState.EnabledWorkspace : EnablementState.EnabledGlobally);2620}2621await this.waitUntilExtensionIsEnabled(extension);2622}26232624return extension;2625}26262627async installInServer(extension: IExtension, server: IExtensionManagementServer, installOptions?: InstallOptions): Promise<void> {2628await this.doInstall(extension, async () => {2629const local = extension.local;2630if (!local) {2631throw new Error('Extension not found');2632}2633if (!extension.gallery) {2634extension = (await this.getExtensions([{ ...extension.identifier, preRelease: local.preRelease }], CancellationToken.None))[0] ?? extension;2635}2636if (extension.gallery) {2637return server.extensionManagementService.installFromGallery(extension.gallery, { installPreReleaseVersion: local.preRelease, ...installOptions });2638}26392640const targetPlatform = await server.extensionManagementService.getTargetPlatform();2641if (!isTargetPlatformCompatible(local.targetPlatform, [local.targetPlatform], targetPlatform)) {2642throw new Error(nls.localize('incompatible', "Can't install '{0}' extension because it is not compatible.", extension.identifier.id));2643}26442645const vsix = await this.extensionManagementService.zip(local);2646try {2647return await server.extensionManagementService.install(vsix);2648} finally {2649try {2650await this.fileService.del(vsix);2651} catch (error) {2652this.logService.error(error);2653}2654}2655});2656}26572658canSetLanguage(extension: IExtension): boolean {2659if (!isWeb) {2660return false;2661}26622663if (!extension.gallery) {2664return false;2665}26662667const locale = getLocale(extension.gallery);2668if (!locale) {2669return false;2670}26712672return true;2673}26742675async setLanguage(extension: IExtension): Promise<void> {2676if (!this.canSetLanguage(extension)) {2677throw new Error('Can not set language');2678}2679const locale = getLocale(extension.gallery!);2680if (locale === language) {2681return;2682}2683const localizedLanguageName = extension.gallery?.properties?.localizedLanguages?.[0];2684return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: localizedLanguageName ?? extension.displayName });2685}26862687setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void> {2688extensions = Array.isArray(extensions) ? extensions : [extensions];2689return this.promptAndSetEnablement(extensions, enablementState);2690}26912692async uninstall(e: IExtension): Promise<void> {2693const extension = e.local ? e : this.local.find(local => areSameExtensions(local.identifier, e.identifier));2694if (!extension?.local) {2695throw new Error('Missing local');2696}26972698if (extension.local.isApplicationScoped && this.userDataProfilesService.profiles.length > 1) {2699const { confirmed } = await this.dialogService.confirm({2700title: nls.localize('uninstallApplicationScoped', "Uninstall Extension"),2701type: Severity.Info,2702message: nls.localize('uninstallApplicationScopedMessage', "Would you like to Uninstall {0} from all profiles?", extension.displayName),2703primaryButton: nls.localize('uninstallAllProfiles', "Uninstall (All Profiles)")2704});2705if (!confirmed) {2706throw new CancellationError();2707}2708}27092710const extensionsToUninstall: UninstallExtensionInfo[] = [{ extension: extension.local }];2711if (!areSameExtensions(extension.identifier, { id: this.productService.defaultChatAgent.extensionId })) {2712for (const packExtension of this.getAllPackedExtensions(extension, this.local)) {2713if (packExtension.local && !extensionsToUninstall.some(e => areSameExtensions(e.extension.identifier, packExtension.identifier))) {2714extensionsToUninstall.push({ extension: packExtension.local });2715}2716}2717}27182719const dependents: ILocalExtension[] = [];2720let extensionsFromAllProfiles: [ILocalExtension, URI][] | undefined;2721for (const { extension } of extensionsToUninstall) {2722const installedExtensions: [ILocalExtension, URI | undefined][] = [];2723if (extension.isApplicationScoped && this.userDataProfilesService.profiles.length > 1) {2724if (!extensionsFromAllProfiles) {2725extensionsFromAllProfiles = [];2726await Promise.allSettled(this.userDataProfilesService.profiles.map(async profile => {2727const installed = await this.extensionManagementService.getInstalled(ExtensionType.User, profile.extensionsResource);2728for (const local of installed) {2729extensionsFromAllProfiles?.push([local, profile.extensionsResource]);2730}2731}));2732}2733installedExtensions.push(...extensionsFromAllProfiles);2734} else {2735for (const { local } of this.local) {2736if (local) {2737installedExtensions.push([local, undefined]);2738}2739}2740}2741for (const [local, profileLocation] of installedExtensions) {2742if (areSameExtensions(local.identifier, extension.identifier)) {2743continue;2744}2745if (!local.manifest.extensionDependencies || local.manifest.extensionDependencies.length === 0) {2746continue;2747}2748if (extension.manifest.extensionPack?.some(id => areSameExtensions({ id }, local.identifier))) {2749continue;2750}2751if (dependents.some(d => d.manifest.extensionPack?.some(id => areSameExtensions({ id }, local.identifier)))) {2752continue;2753}2754if (local.manifest.extensionDependencies.some(dep => areSameExtensions(extension.identifier, { id: dep }))) {2755dependents.push(local);2756extensionsToUninstall.push({ extension: local, options: { profileLocation } });2757}2758}2759}27602761if (dependents.length) {2762const { result } = await this.dialogService.prompt({2763title: nls.localize('uninstallDependents', "Uninstall Extension with Dependents"),2764type: Severity.Warning,2765message: this.getErrorMessageForUninstallingAnExtensionWithDependents(extension, dependents),2766buttons: [{2767label: nls.localize('uninstallAll', "Uninstall All"),2768run: () => true2769}],2770cancelButton: {2771run: () => false2772}2773});2774if (!result) {2775throw new CancellationError();2776}2777}27782779return this.withProgress({2780location: ProgressLocation.Extensions,2781title: nls.localize('uninstallingExtension', 'Uninstalling extension...'),2782source: `${extension.identifier.id}`2783}, () => this.extensionManagementService.uninstallExtensions(extensionsToUninstall).then(() => undefined));2784}27852786private getAllPackedExtensions(extension: IExtension, installed: IExtension[], checked: IExtension[] = []): IExtension[] {2787if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) {2788return [];2789}2790checked.push(extension);2791const extensionsPack = extension.extensionPack ?? [];2792if (extensionsPack.length) {2793const packedExtensions: IExtension[] = [];2794for (const i of installed) {2795if (!i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier))) {2796packedExtensions.push(i);2797}2798}2799const packOfPackedExtensions: IExtension[] = [];2800for (const packedExtension of packedExtensions) {2801packOfPackedExtensions.push(...this.getAllPackedExtensions(packedExtension, installed, checked));2802}2803return [...packedExtensions, ...packOfPackedExtensions];2804}2805return [];2806}28072808private getErrorMessageForUninstallingAnExtensionWithDependents(extension: IExtension, dependents: ILocalExtension[]): string {2809if (dependents.length === 1) {2810return nls.localize('singleDependentUninstallError', "Cannot uninstall '{0}' extension alone. '{1}' extension depends on this. Do you want to uninstall all these extensions?", extension.displayName, dependents[0].manifest.displayName);2811}2812if (dependents.length === 2) {2813return nls.localize('twoDependentsUninstallError', "Cannot uninstall '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to uninstall all these extensions?",2814extension.displayName, dependents[0].manifest.displayName, dependents[1].manifest.displayName);2815}2816return nls.localize('multipleDependentsUninstallError', "Cannot uninstall '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to uninstall all these extensions?",2817extension.displayName, dependents[0].manifest.displayName, dependents[1].manifest.displayName);2818}28192820isExtensionIgnoredToSync(extension: IExtension): boolean {2821return extension.local ? !this.isInstalledExtensionSynced(extension.local)2822: this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id);2823}28242825async togglePreRelease(extension: IExtension): Promise<void> {2826if (!extension.local) {2827return;2828}2829if (extension.preRelease !== extension.isPreReleaseVersion) {2830await this.extensionManagementService.updateMetadata(extension.local, { preRelease: !extension.preRelease });2831return;2832}2833await this.install(extension, { installPreReleaseVersion: !extension.preRelease, preRelease: !extension.preRelease });2834}28352836async toggleExtensionIgnoredToSync(extension: IExtension): Promise<void> {2837const extensionsIncludingPackedExtensions = [extension, ...this.getAllPackedExtensions(extension, this.local)];2838// Updated in sync to prevent race conditions2839for (const e of extensionsIncludingPackedExtensions) {2840const isIgnored = this.isExtensionIgnoredToSync(e);2841if (e.local && isIgnored && e.local.isMachineScoped) {2842await this.extensionManagementService.updateMetadata(e.local, { isMachineScoped: false });2843} else {2844await this.extensionsSyncManagementService.updateIgnoredExtensions(e.identifier.id, !isIgnored);2845}2846}2847await this.userDataAutoSyncService.triggerSync(['IgnoredExtensionsUpdated']);2848}28492850async toggleApplyExtensionToAllProfiles(extension: IExtension): Promise<void> {2851const extensionsIncludingPackedExtensions = [extension, ...this.getAllPackedExtensions(extension, this.local)];2852const allExtensionServers = this.getAllExtensionServers();2853await Promise.allSettled(extensionsIncludingPackedExtensions.map(async e => {2854if (!e.local || isApplicationScopedExtension(e.local.manifest) || e.isBuiltin) {2855return;2856}2857const isApplicationScoped = e.local.isApplicationScoped;2858await Promise.all(allExtensionServers.map(async extensionServer => {2859const local = extensionServer.local.find(local => areSameExtensions(e.identifier, local.identifier))?.local;2860if (local && local.isApplicationScoped === isApplicationScoped) {2861await this.extensionManagementService.toggleApplicationScope(local, this.userDataProfileService.currentProfile.extensionsResource);2862}2863}));2864}));2865}28662867private getAllExtensionServers(): Extensions[] {2868const extensions: Extensions[] = [];2869if (this.localExtensions) {2870extensions.push(this.localExtensions);2871}2872if (this.remoteExtensions) {2873extensions.push(this.remoteExtensions);2874}2875if (this.webExtensions) {2876extensions.push(this.webExtensions);2877}2878return extensions;2879}28802881private isInstalledExtensionSynced(extension: ILocalExtension): boolean {2882if (extension.isMachineScoped) {2883return false;2884}2885if (this.extensionsSyncManagementService.hasToAlwaysSyncExtension(extension.identifier.id)) {2886return true;2887}2888return !this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id);2889}28902891private doInstall(extension: IExtension | undefined, installTask: () => Promise<ILocalExtension>, progressLocation?: ProgressLocation | string): Promise<IExtension> {2892const title = extension ? nls.localize('installing named extension', "Installing '{0}' extension...", extension.displayName) : nls.localize('installing extension', 'Installing extension...');2893return this.withProgress({2894location: progressLocation ?? ProgressLocation.Extensions,2895title2896}, async () => {2897try {2898if (extension) {2899this.installing.push(extension);2900this._onChange.fire(extension);2901}2902const local = await installTask();2903return await this.waitAndGetInstalledExtension(local.identifier);2904} finally {2905if (extension) {2906this.installing = this.installing.filter(e => e !== extension);2907// Trigger the change without passing the extension because it is replaced by a new instance.2908this._onChange.fire(undefined);2909}2910}2911});2912}29132914private async installFromVSIX(vsix: URI, installOptions: InstallOptions): Promise<ILocalExtension> {2915const manifest = await this.extensionManagementService.getManifest(vsix);2916const existingExtension = this.local.find(local => areSameExtensions(local.identifier, { id: getGalleryExtensionId(manifest.publisher, manifest.name) }));2917if (existingExtension) {2918installOptions = installOptions || {};2919if (existingExtension.latestVersion === manifest.version) {2920installOptions.pinned = installOptions.pinned ?? (existingExtension.local?.pinned || !this.shouldAutoUpdateExtension(existingExtension));2921} else {2922installOptions.installGivenVersion = true;2923}2924}2925return this.extensionManagementService.installVSIX(vsix, manifest, installOptions);2926}29272928private installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions: InstallExtensionOptions, servers: IExtensionManagementServer[] | undefined): Promise<ILocalExtension> {2929installOptions = installOptions ?? {};2930installOptions.pinned = installOptions.pinned ?? (extension.local?.pinned || !this.shouldAutoUpdateExtension(extension));2931if (extension.local && !servers) {2932installOptions.productVersion = this.getProductVersion();2933installOptions.operation = InstallOperation.Update;2934return this.extensionManagementService.updateFromGallery(gallery, extension.local, installOptions);2935} else {2936return this.extensionManagementService.installFromGallery(gallery, installOptions, servers);2937}2938}29392940private async waitAndGetInstalledExtension(identifier: IExtensionIdentifier): Promise<IExtension> {2941let installedExtension = this.local.find(local => areSameExtensions(local.identifier, identifier));2942if (!installedExtension) {2943await Event.toPromise(Event.filter(this.onChange, e => !!e && this.local.some(local => areSameExtensions(local.identifier, identifier))));2944}2945installedExtension = this.local.find(local => areSameExtensions(local.identifier, identifier));2946if (!installedExtension) {2947// This should not happen2948throw new Error('Extension should have been installed');2949}2950return installedExtension;2951}29522953private async waitUntilExtensionIsEnabled(extension: IExtension): Promise<void> {2954if (this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension.identifier.id))) {2955return;2956}2957if (!extension.local || !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) {2958return;2959}2960await new Promise<void>((c, e) => {2961const disposable = this.extensionService.onDidChangeExtensions(() => {2962try {2963if (this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension.identifier.id))) {2964disposable.dispose();2965c();2966}2967} catch (error) {2968e(error);2969}2970});2971});2972}29732974private promptAndSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise<any> {2975const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;2976if (enable) {2977const allDependenciesAndPackedExtensions = this.getExtensionsRecursively(extensions, this.local, enablementState, { dependencies: true, pack: true });2978return this.checkAndSetEnablement(extensions, allDependenciesAndPackedExtensions, enablementState);2979} else {2980const packedExtensions = this.getExtensionsRecursively(extensions, this.local, enablementState, { dependencies: false, pack: true });2981if (packedExtensions.length) {2982return this.checkAndSetEnablement(extensions, packedExtensions, enablementState);2983}2984return this.checkAndSetEnablement(extensions, [], enablementState);2985}2986}29872988private async checkAndSetEnablement(extensions: IExtension[], otherExtensions: IExtension[], enablementState: EnablementState): Promise<any> {2989const allExtensions = [...extensions, ...otherExtensions];2990const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;2991if (!enable) {2992for (const extension of extensions) {2993const dependents = this.getDependentsAfterDisablement(extension, allExtensions, this.local);2994if (dependents.length) {2995const { result } = await this.dialogService.prompt({2996title: nls.localize('disableDependents', "Disable Extension with Dependents"),2997type: Severity.Warning,2998message: this.getDependentsErrorMessageForDisablement(extension, allExtensions, dependents),2999buttons: [{3000label: nls.localize('disable all', 'Disable All'),3001run: () => true3002}],3003cancelButton: {3004run: () => false3005}3006});3007if (!result) {3008throw new CancellationError();3009}3010await this.checkAndSetEnablement(dependents, [extension], enablementState);3011}3012}3013}3014return this.doSetEnablement(allExtensions, enablementState);3015}30163017private getExtensionsRecursively(extensions: IExtension[], installed: IExtension[], enablementState: EnablementState, options: { dependencies: boolean; pack: boolean }, checked: IExtension[] = []): IExtension[] {3018const toCheck = extensions.filter(e => checked.indexOf(e) === -1);3019if (toCheck.length) {3020for (const extension of toCheck) {3021checked.push(extension);3022}3023const extensionsToEanbleOrDisable = installed.filter(i => {3024if (checked.indexOf(i) !== -1) {3025return false;3026}3027const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;3028const isExtensionEnabled = i.enablementState === EnablementState.EnabledGlobally || i.enablementState === EnablementState.EnabledWorkspace;3029if (enable === isExtensionEnabled) {3030return false;3031}3032return (enable || !i.isBuiltin) // Include all Extensions for enablement and only non builtin extensions for disablement3033&& (options.dependencies || options.pack)3034&& extensions.some(extension =>3035(options.dependencies && extension.dependencies.some(id => areSameExtensions({ id }, i.identifier)))3036|| (options.pack && extension.extensionPack.some(id => areSameExtensions({ id }, i.identifier)))3037);3038});3039if (extensionsToEanbleOrDisable.length) {3040extensionsToEanbleOrDisable.push(...this.getExtensionsRecursively(extensionsToEanbleOrDisable, installed, enablementState, options, checked));3041}3042return extensionsToEanbleOrDisable;3043}3044return [];3045}30463047private getDependentsAfterDisablement(extension: IExtension, extensionsToDisable: IExtension[], installed: IExtension[]): IExtension[] {3048return installed.filter(i => {3049if (i.dependencies.length === 0) {3050return false;3051}3052if (i === extension) {3053return false;3054}3055if (!this.extensionEnablementService.isEnabledEnablementState(i.enablementState)) {3056return false;3057}3058if (extensionsToDisable.indexOf(i) !== -1) {3059return false;3060}3061return i.dependencies.some(dep => [extension, ...extensionsToDisable].some(d => areSameExtensions(d.identifier, { id: dep })));3062});3063}30643065private getDependentsErrorMessageForDisablement(extension: IExtension, allDisabledExtensions: IExtension[], dependents: IExtension[]): string {3066for (const e of [extension, ...allDisabledExtensions]) {3067const dependentsOfTheExtension = dependents.filter(d => d.dependencies.some(id => areSameExtensions({ id }, e.identifier)));3068if (dependentsOfTheExtension.length) {3069return this.getErrorMessageForDisablingAnExtensionWithDependents(e, dependentsOfTheExtension);3070}3071}3072return '';3073}30743075private getErrorMessageForDisablingAnExtensionWithDependents(extension: IExtension, dependents: IExtension[]): string {3076if (dependents.length === 1) {3077return nls.localize('singleDependentError', "Cannot disable '{0}' extension alone. '{1}' extension depends on this. Do you want to disable all these extensions?", extension.displayName, dependents[0].displayName);3078}3079if (dependents.length === 2) {3080return nls.localize('twoDependentsError', "Cannot disable '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to disable all these extensions?",3081extension.displayName, dependents[0].displayName, dependents[1].displayName);3082}3083return nls.localize('multipleDependentsError', "Cannot disable '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to disable all these extensions?",3084extension.displayName, dependents[0].displayName, dependents[1].displayName);3085}30863087private async doSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise<boolean[]> {3088return await this.extensionEnablementService.setEnablement(extensions.map(e => e.local!), enablementState);3089}30903091// Current service reports progress when installing/uninstalling extensions3092// This is to report progress for other sources of extension install/uninstall changes3093// Since we cannot differentiate between the two, we report progress for all extension install/uninstall changes3094private _activityCallBack: ((value: void) => void) | undefined;3095private reportProgressFromOtherSources(): void {3096if (this.installed.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling)) {3097if (!this._activityCallBack) {3098this.withProgress({ location: ProgressLocation.Extensions }, () => new Promise(resolve => this._activityCallBack = resolve));3099}3100} else {3101this._activityCallBack?.();3102this._activityCallBack = undefined;3103}3104}31053106private withProgress<T>(options: IProgressOptions, task: () => Promise<T>): Promise<T> {3107return this.progressService.withProgress(options, async () => {3108const cancelableTask = createCancelablePromise(() => task());3109this.tasksInProgress.push(cancelableTask);3110try {3111return await cancelableTask;3112} finally {3113const index = this.tasksInProgress.indexOf(cancelableTask);3114if (index !== -1) {3115this.tasksInProgress.splice(index, 1);3116}3117}3118});3119}31203121private onError(err: any): void {3122if (isCancellationError(err)) {3123return;3124}31253126const message = err && err.message || '';31273128if (/getaddrinfo ENOTFOUND|getaddrinfo ENOENT|connect EACCES|connect ECONNREFUSED/.test(message)) {3129return;3130}31313132this.notificationService.error(err);3133}31343135handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {3136if (!/^extension/.test(uri.path)) {3137return Promise.resolve(false);3138}31393140this.onOpenExtensionUrl(uri);3141return Promise.resolve(true);3142}31433144private onOpenExtensionUrl(uri: URI): void {3145const match = /^extension\/([^/]+)$/.exec(uri.path);31463147if (!match) {3148return;3149}31503151const extensionId = match[1];31523153this.queryLocal().then(async local => {3154let extension = local.find(local => areSameExtensions(local.identifier, { id: extensionId }));3155if (!extension) {3156[extension] = await this.getExtensions([{ id: extensionId }], { source: 'uri' }, CancellationToken.None);3157}3158if (extension) {3159await this.hostService.focus(mainWindow);3160await this.open(extension);3161}3162}).then(undefined, error => this.onError(error));3163}31643165private getPublishersToAutoUpdate(): string[] {3166return this.getEnabledAutoUpdateExtensions().filter(id => !EXTENSION_IDENTIFIER_REGEX.test(id));3167}31683169getEnabledAutoUpdateExtensions(): string[] {3170try {3171const parsedValue = JSON.parse(this.enabledAuotUpdateExtensionsValue);3172if (Array.isArray(parsedValue)) {3173return parsedValue;3174}3175} catch (e) { /* Ignore */ }3176return [];3177}31783179private setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions: string[]): void {3180this.enabledAuotUpdateExtensionsValue = JSON.stringify(enabledAutoUpdateExtensions);3181}31823183private _enabledAutoUpdateExtensionsValue: string | undefined;3184private get enabledAuotUpdateExtensionsValue(): string {3185if (!this._enabledAutoUpdateExtensionsValue) {3186this._enabledAutoUpdateExtensionsValue = this.getEnabledAutoUpdateExtensionsValue();3187}31883189return this._enabledAutoUpdateExtensionsValue;3190}31913192private set enabledAuotUpdateExtensionsValue(enabledAuotUpdateExtensionsValue: string) {3193if (this.enabledAuotUpdateExtensionsValue !== enabledAuotUpdateExtensionsValue) {3194this._enabledAutoUpdateExtensionsValue = enabledAuotUpdateExtensionsValue;3195this.setEnabledAutoUpdateExtensionsValue(enabledAuotUpdateExtensionsValue);3196}3197}31983199private getEnabledAutoUpdateExtensionsValue(): string {3200return this.storageService.get(EXTENSIONS_AUTO_UPDATE_KEY, StorageScope.APPLICATION, '[]');3201}32023203private setEnabledAutoUpdateExtensionsValue(value: string): void {3204this.storageService.store(EXTENSIONS_AUTO_UPDATE_KEY, value, StorageScope.APPLICATION, StorageTarget.USER);3205}32063207getDisabledAutoUpdateExtensions(): string[] {3208try {3209const parsedValue = JSON.parse(this.disabledAutoUpdateExtensionsValue);3210if (Array.isArray(parsedValue)) {3211return parsedValue;3212}3213} catch (e) { /* Ignore */ }3214return [];3215}32163217private setDisabledAutoUpdateExtensions(disabledAutoUpdateExtensions: string[]): void {3218this.disabledAutoUpdateExtensionsValue = JSON.stringify(disabledAutoUpdateExtensions);3219}32203221private _disabledAutoUpdateExtensionsValue: string | undefined;3222private get disabledAutoUpdateExtensionsValue(): string {3223if (!this._disabledAutoUpdateExtensionsValue) {3224this._disabledAutoUpdateExtensionsValue = this.getDisabledAutoUpdateExtensionsValue();3225}32263227return this._disabledAutoUpdateExtensionsValue;3228}32293230private set disabledAutoUpdateExtensionsValue(disabledAutoUpdateExtensionsValue: string) {3231if (this.disabledAutoUpdateExtensionsValue !== disabledAutoUpdateExtensionsValue) {3232this._disabledAutoUpdateExtensionsValue = disabledAutoUpdateExtensionsValue;3233this.setDisabledAutoUpdateExtensionsValue(disabledAutoUpdateExtensionsValue);3234}3235}32363237private getDisabledAutoUpdateExtensionsValue(): string {3238return this.storageService.get(EXTENSIONS_DONOT_AUTO_UPDATE_KEY, StorageScope.APPLICATION, '[]');3239}32403241private setDisabledAutoUpdateExtensionsValue(value: string): void {3242this.storageService.store(EXTENSIONS_DONOT_AUTO_UPDATE_KEY, value, StorageScope.APPLICATION, StorageTarget.USER);3243}32443245private getDismissedNotifications(): string[] {3246try {3247const parsedValue = JSON.parse(this.dismissedNotificationsValue);3248if (Array.isArray(parsedValue)) {3249return parsedValue;3250}3251} catch (e) { /* Ignore */ }3252return [];3253}32543255private setDismissedNotifications(dismissedNotifications: string[]): void {3256this.dismissedNotificationsValue = JSON.stringify(dismissedNotifications);3257}32583259private _dismissedNotificationsValue: string | undefined;3260private get dismissedNotificationsValue(): string {3261if (!this._dismissedNotificationsValue) {3262this._dismissedNotificationsValue = this.getDismissedNotificationsValue();3263}32643265return this._dismissedNotificationsValue;3266}32673268private set dismissedNotificationsValue(dismissedNotificationsValue: string) {3269if (this.dismissedNotificationsValue !== dismissedNotificationsValue) {3270this._dismissedNotificationsValue = dismissedNotificationsValue;3271this.setDismissedNotificationsValue(dismissedNotificationsValue);3272}3273}32743275private getDismissedNotificationsValue(): string {3276return this.storageService.get(EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, StorageScope.PROFILE, '[]');3277}32783279private setDismissedNotificationsValue(value: string): void {3280this.storageService.store(EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, value, StorageScope.PROFILE, StorageTarget.USER);3281}32823283}328432853286