Path: blob/main/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
3296 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 { IEditorService, SIDE_GROUP, ACTIVE_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';7677interface IExtensionStateProvider<T> {78(extension: Extension): T;79}8081interface InstalledExtensionsEvent {82readonly extensionIds: TelemetryTrustedValue<string>;83readonly count: number;84}85type ExtensionsLoadClassification = {86owner: 'digitarald';87comment: 'Helps to understand which extensions are the most actively used.';88readonly extensionIds: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The list of extension ids that are installed.' };89readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The number of extensions that are installed.' };90};9192export class Extension implements IExtension {9394public enablementState: EnablementState = EnablementState.EnabledGlobally;9596private galleryResourcesCache = new Map<string, any>();9798private _missingFromGallery: boolean | undefined;99100constructor(101private stateProvider: IExtensionStateProvider<ExtensionState>,102private runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,103public readonly server: IExtensionManagementServer | undefined,104public local: ILocalExtension | undefined,105private _gallery: IGalleryExtension | undefined,106private readonly resourceExtensionInfo: { resourceExtension: IResourceExtension; isWorkspaceScoped: boolean } | undefined,107@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,108@ITelemetryService private readonly telemetryService: ITelemetryService,109@ILogService private readonly logService: ILogService,110@IFileService private readonly fileService: IFileService,111@IProductService private readonly productService: IProductService112) {113}114115get resourceExtension(): IResourceExtension | undefined {116if (this.resourceExtensionInfo) {117return this.resourceExtensionInfo.resourceExtension;118}119if (this.local?.isWorkspaceScoped) {120return {121type: 'resource',122identifier: this.local.identifier,123location: this.local.location,124manifest: this.local.manifest,125changelogUri: this.local.changelogUrl,126readmeUri: this.local.readmeUrl,127};128}129return undefined;130}131132get gallery(): IGalleryExtension | undefined {133return this._gallery;134}135136set gallery(gallery: IGalleryExtension | undefined) {137this._gallery = gallery;138this.galleryResourcesCache.clear();139}140141get missingFromGallery(): boolean {142return !!this._missingFromGallery;143}144145set missingFromGallery(missing: boolean) {146this._missingFromGallery = missing;147}148149get type(): ExtensionType {150return this.local ? this.local.type : ExtensionType.User;151}152153get isBuiltin(): boolean {154return this.local ? this.local.isBuiltin : false;155}156157get isWorkspaceScoped(): boolean {158if (this.local) {159return this.local.isWorkspaceScoped;160}161if (this.resourceExtensionInfo) {162return this.resourceExtensionInfo.isWorkspaceScoped;163}164return false;165}166167get name(): string {168if (this.gallery) {169return this.gallery.name;170}171return this.getManifestFromLocalOrResource()?.name ?? '';172}173174get displayName(): string {175if (this.gallery) {176return this.gallery.displayName || this.gallery.name;177}178179return this.getManifestFromLocalOrResource()?.displayName ?? this.name;180}181182get identifier(): IExtensionIdentifier {183if (this.gallery) {184return this.gallery.identifier;185}186if (this.resourceExtension) {187return this.resourceExtension.identifier;188}189return this.local?.identifier ?? { id: '' };190}191192get uuid(): string | undefined {193return this.gallery ? this.gallery.identifier.uuid : this.local?.identifier.uuid;194}195196get publisher(): string {197if (this.gallery) {198return this.gallery.publisher;199}200return this.getManifestFromLocalOrResource()?.publisher ?? '';201}202203get publisherDisplayName(): string {204if (this.gallery) {205return this.gallery.publisherDisplayName || this.gallery.publisher;206}207208if (this.local?.publisherDisplayName) {209return this.local.publisherDisplayName;210}211212return this.publisher;213}214215get publisherUrl(): URI | undefined {216return this.gallery?.publisherLink ? URI.parse(this.gallery.publisherLink) : undefined;217}218219get publisherDomain(): { link: string; verified: boolean } | undefined {220return this.gallery?.publisherDomain;221}222223get publisherSponsorLink(): URI | undefined {224return this.gallery?.publisherSponsorLink ? URI.parse(this.gallery.publisherSponsorLink) : undefined;225}226227get version(): string {228return this.local ? this.local.manifest.version : this.latestVersion;229}230231get private(): boolean {232return this.gallery ? this.gallery.private : this.local ? this.local.private : false;233}234235get pinned(): boolean {236return !!this.local?.pinned;237}238239get latestVersion(): string {240return this.gallery ? this.gallery.version : this.getManifestFromLocalOrResource()?.version ?? '';241}242243get description(): string {244return this.gallery ? this.gallery.description : this.getManifestFromLocalOrResource()?.description ?? '';245}246247get url(): string | undefined {248return this.gallery?.detailsLink;249}250251get iconUrl(): string | undefined {252return this.galleryIconUrl || this.resourceExtensionIconUrl || this.localIconUrl || this.defaultIconUrl;253}254255get iconUrlFallback(): string | undefined {256return this.gallery?.assets.icon?.fallbackUri;257}258259private get localIconUrl(): string | undefined {260if (this.local && this.local.manifest.icon) {261return FileAccess.uriToBrowserUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true);262}263return undefined;264}265266private get resourceExtensionIconUrl(): string | undefined {267if (this.resourceExtension?.manifest.icon) {268return FileAccess.uriToBrowserUri(resources.joinPath(this.resourceExtension.location, this.resourceExtension.manifest.icon)).toString(true);269}270return undefined;271}272273private get galleryIconUrl(): string | undefined {274return this.gallery?.assets.icon?.uri;275}276277private get defaultIconUrl(): string | undefined {278if (this.type === ExtensionType.System && this.local) {279if (this.local.manifest && this.local.manifest.contributes) {280if (Array.isArray(this.local.manifest.contributes.themes) && this.local.manifest.contributes.themes.length) {281return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/theme-icon.png').toString(true);282}283if (Array.isArray(this.local.manifest.contributes.grammars) && this.local.manifest.contributes.grammars.length) {284return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/language-icon.svg').toString(true);285}286}287}288return undefined;289}290291get repository(): string | undefined {292return this.gallery && this.gallery.assets.repository ? this.gallery.assets.repository.uri : undefined;293}294295get licenseUrl(): string | undefined {296return this.gallery && this.gallery.assets.license ? this.gallery.assets.license.uri : undefined;297}298299get supportUrl(): string | undefined {300return this.gallery && this.gallery.supportLink ? this.gallery.supportLink : undefined;301}302303get state(): ExtensionState {304return this.stateProvider(this);305}306307private malicious: MaliciousExtensionInfo | undefined;308public get isMalicious(): boolean | undefined {309return !!this.malicious || this.enablementState === EnablementState.DisabledByMalicious;310}311312public get maliciousInfoLink(): string | undefined {313return this.malicious?.learnMoreLink;314}315316public deprecationInfo: IDeprecationInfo | undefined;317318get installCount(): number | undefined {319return this.gallery ? this.gallery.installCount : undefined;320}321322get rating(): number | undefined {323return this.gallery ? this.gallery.rating : undefined;324}325326get ratingCount(): number | undefined {327return this.gallery ? this.gallery.ratingCount : undefined;328}329330get ratingUrl(): string | undefined {331return this.gallery?.ratingLink;332}333334get outdated(): boolean {335try {336if (!this.gallery || !this.local) {337return false;338}339// Do not allow updating system extensions in stable340if (this.type === ExtensionType.System && this.productService.quality === 'stable') {341return false;342}343if (!this.local.preRelease && this.gallery.properties.isPreReleaseVersion) {344return false;345}346if (semver.gt(this.latestVersion, this.version)) {347return true;348}349if (this.outdatedTargetPlatform) {350return true;351}352} catch (error) {353/* Ignore */354}355return false;356}357358get outdatedTargetPlatform(): boolean {359return !!this.local && !!this.gallery360&& ![TargetPlatform.UNDEFINED, TargetPlatform.WEB].includes(this.local.targetPlatform)361&& this.gallery.properties.targetPlatform !== TargetPlatform.WEB362&& this.local.targetPlatform !== this.gallery.properties.targetPlatform363&& semver.eq(this.latestVersion, this.version);364}365366get runtimeState(): ExtensionRuntimeState | undefined {367return this.runtimeStateProvider(this);368}369370get telemetryData(): any {371const { local, gallery } = this;372373if (gallery) {374return getGalleryExtensionTelemetryData(gallery);375} else if (local) {376return getLocalExtensionTelemetryData(local);377} else {378return {};379}380}381382get preview(): boolean {383return this.local?.manifest.preview ?? this.gallery?.preview ?? false;384}385386get preRelease(): boolean {387return !!this.local?.preRelease;388}389390get isPreReleaseVersion(): boolean {391if (this.local) {392return this.local.isPreReleaseVersion;393}394return !!this.gallery?.properties.isPreReleaseVersion;395}396397get hasPreReleaseVersion(): boolean {398return this.gallery ? this.gallery.hasPreReleaseVersion : !!this.local?.hasPreReleaseVersion;399}400401get hasReleaseVersion(): boolean {402return !!this.resourceExtension || !!this.gallery?.hasReleaseVersion;403}404405private getLocal(): ILocalExtension | undefined {406return this.local && !this.outdated ? this.local : undefined;407}408409async getManifest(token: CancellationToken): Promise<IExtensionManifest | null> {410const local = this.getLocal();411if (local) {412return local.manifest;413}414415if (this.gallery) {416return this.getGalleryManifest(token);417}418419if (this.resourceExtension) {420return this.resourceExtension.manifest;421}422423return null;424}425426async getGalleryManifest(token: CancellationToken = CancellationToken.None): Promise<IExtensionManifest | null> {427if (this.gallery) {428let cache = this.galleryResourcesCache.get('manifest');429if (!cache) {430if (this.gallery.assets.manifest) {431this.galleryResourcesCache.set('manifest', cache = this.galleryService.getManifest(this.gallery, token)432.catch(e => {433this.galleryResourcesCache.delete('manifest');434throw e;435}));436} else {437this.logService.error(nls.localize('Manifest is not found', "Manifest is not found"), this.identifier.id);438}439}440return cache;441}442return null;443}444445hasReadme(): boolean {446if (this.local && this.local.readmeUrl) {447return true;448}449450if (this.gallery && this.gallery.assets.readme) {451return true;452}453454if (this.resourceExtension?.readmeUri) {455return true;456}457458return this.type === ExtensionType.System;459}460461async getReadme(token: CancellationToken): Promise<string> {462const local = this.getLocal();463if (local?.readmeUrl) {464const content = await this.fileService.readFile(local.readmeUrl);465return content.value.toString();466}467468if (this.gallery) {469if (this.gallery.assets.readme) {470return this.galleryService.getReadme(this.gallery, token);471}472this.telemetryService.publicLog('extensions:NotFoundReadMe', this.telemetryData);473}474475if (this.type === ExtensionType.System) {476return Promise.resolve(`# ${this.displayName || this.name}477**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.478## Features479${this.description}480`);481}482483if (this.resourceExtension?.readmeUri) {484const content = await this.fileService.readFile(this.resourceExtension?.readmeUri);485return content.value.toString();486}487488return Promise.reject(new Error('not available'));489}490491hasChangelog(): boolean {492if (this.local && this.local.changelogUrl) {493return true;494}495496if (this.gallery && this.gallery.assets.changelog) {497return true;498}499500return this.type === ExtensionType.System;501}502503async getChangelog(token: CancellationToken): Promise<string> {504const local = this.getLocal();505if (local?.changelogUrl) {506const content = await this.fileService.readFile(local.changelogUrl);507return content.value.toString();508}509510if (this.gallery?.assets.changelog) {511return this.galleryService.getChangelog(this.gallery, token);512}513514if (this.type === ExtensionType.System) {515return Promise.resolve(`Please check the [VS Code Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`);516}517518return Promise.reject(new Error('not available'));519}520521get categories(): readonly string[] {522const { local, gallery, resourceExtension } = this;523if (local && local.manifest.categories && !this.outdated) {524return local.manifest.categories;525}526if (gallery) {527return gallery.categories;528}529if (resourceExtension) {530return resourceExtension.manifest.categories ?? [];531}532return [];533}534535get tags(): readonly string[] {536const { gallery } = this;537if (gallery) {538return gallery.tags.filter(tag => !tag.startsWith('_'));539}540return [];541}542543get dependencies(): string[] {544const { local, gallery, resourceExtension } = this;545if (local && local.manifest.extensionDependencies && !this.outdated) {546return local.manifest.extensionDependencies;547}548if (gallery) {549return gallery.properties.dependencies || [];550}551if (resourceExtension) {552return resourceExtension.manifest.extensionDependencies || [];553}554return [];555}556557get extensionPack(): string[] {558const { local, gallery, resourceExtension } = this;559if (local && local.manifest.extensionPack && !this.outdated) {560return local.manifest.extensionPack;561}562if (gallery) {563return gallery.properties.extensionPack || [];564}565if (resourceExtension) {566return resourceExtension.manifest.extensionPack || [];567}568return [];569}570571setExtensionsControlManifest(extensionsControlManifest: IExtensionsControlManifest): void {572this.malicious = findMatchingMaliciousEntry(this.identifier, extensionsControlManifest.malicious);573this.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[this.identifier.id.toLowerCase()] : undefined;574}575576private getManifestFromLocalOrResource(): IExtensionManifest | null {577if (this.local) {578return this.local.manifest;579}580if (this.resourceExtension) {581return this.resourceExtension.manifest;582}583return null;584}585}586587const EXTENSIONS_AUTO_UPDATE_KEY = 'extensions.autoUpdate';588const EXTENSIONS_DONOT_AUTO_UPDATE_KEY = 'extensions.donotAutoUpdate';589const EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY = 'extensions.dismissedNotifications';590591class Extensions extends Disposable {592593private readonly _onChange = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>());594get onChange() { return this._onChange.event; }595596private readonly _onReset = this._register(new Emitter<void>());597get onReset() { return this._onReset.event; }598599private installing: Extension[] = [];600private uninstalling: Extension[] = [];601private installed: Extension[] = [];602603constructor(604readonly server: IExtensionManagementServer,605private readonly stateProvider: IExtensionStateProvider<ExtensionState>,606private readonly runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,607private readonly isWorkspaceServer: boolean,608@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,609@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,610@IWorkbenchExtensionManagementService private readonly workbenchExtensionManagementService: IWorkbenchExtensionManagementService,611@ITelemetryService private readonly telemetryService: ITelemetryService,612@IInstantiationService private readonly instantiationService: IInstantiationService613) {614super();615this._register(server.extensionManagementService.onInstallExtension(e => this.onInstallExtension(e)));616this._register(server.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));617this._register(server.extensionManagementService.onUninstallExtension(e => this.onUninstallExtension(e.identifier)));618this._register(server.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));619this._register(server.extensionManagementService.onDidUpdateExtensionMetadata(e => this.onDidUpdateExtensionMetadata(e.local)));620this._register(server.extensionManagementService.onDidChangeProfile(() => this.reset()));621this._register(extensionEnablementService.onEnablementChanged(e => this.onEnablementChanged(e)));622this._register(Event.any(this.onChange, this.onReset)(() => this._local = undefined));623if (this.isWorkspaceServer) {624this._register(this.workbenchExtensionManagementService.onInstallExtension(e => {625if (e.workspaceScoped) {626this.onInstallExtension(e);627}628}));629this._register(this.workbenchExtensionManagementService.onDidInstallExtensions(e => {630const result = e.filter(e => e.workspaceScoped);631if (result.length) {632this.onDidInstallExtensions(result);633}634}));635this._register(this.workbenchExtensionManagementService.onUninstallExtension(e => {636if (e.workspaceScoped) {637this.onUninstallExtension(e.identifier);638}639}));640this._register(this.workbenchExtensionManagementService.onDidUninstallExtension(e => {641if (e.workspaceScoped) {642this.onDidUninstallExtension(e);643}644}));645}646}647648private _local: Extension[] | undefined;649get local(): Extension[] {650if (!this._local) {651this._local = [];652for (const extension of this.installed) {653this._local.push(extension);654}655for (const extension of this.installing) {656if (!this.installed.some(installed => areSameExtensions(installed.identifier, extension.identifier))) {657this._local.push(extension);658}659}660}661return this._local;662}663664async queryInstalled(productVersion: IProductVersion): Promise<IExtension[]> {665await this.fetchInstalledExtensions(productVersion);666this._onChange.fire(undefined);667return this.local;668}669670async syncInstalledExtensionsWithGallery(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion, flagExtensionsMissingFromGallery?: IExtensionInfo[]): Promise<void> {671const extensions = await this.mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions, productVersion);672for (const [extension, gallery] of extensions) {673// update metadata of the extension if it does not exist674if (extension.local && !extension.local.identifier.uuid) {675extension.local = await this.updateMetadata(extension.local, gallery);676}677if (!extension.gallery || extension.gallery.version !== gallery.version || extension.gallery.properties.targetPlatform !== gallery.properties.targetPlatform) {678extension.gallery = gallery;679this._onChange.fire({ extension });680}681}682// Detect extensions that do not have a corresponding gallery entry.683if (flagExtensionsMissingFromGallery) {684const extensionsToQuery = [];685for (const extension of this.local) {686// Extension is already paired with a gallery object687if (extension.gallery) {688continue;689}690// Already flagged as missing from gallery691if (extension.missingFromGallery) {692continue;693}694// A UUID indicates extension originated from gallery695if (!extension.identifier.uuid) {696continue;697}698// Extension is not present in the set we are concerned about699if (!flagExtensionsMissingFromGallery.some(f => areSameExtensions(f, extension.identifier))) {700continue;701}702extensionsToQuery.push(extension);703}704if (extensionsToQuery.length) {705const queryResult = await this.galleryService.getExtensions(extensionsToQuery.map(e => ({ ...e.identifier, version: e.version })), CancellationToken.None);706const queriedIds: string[] = [];707const missingIds: string[] = [];708for (const extension of extensionsToQuery) {709queriedIds.push(extension.identifier.id);710const gallery = queryResult.find(g => areSameExtensions(g.identifier, extension.identifier));711if (gallery) {712extension.gallery = gallery;713} else {714extension.missingFromGallery = true;715missingIds.push(extension.identifier.id);716}717this._onChange.fire({ extension });718}719type MissingFromGalleryClassification = {720owner: 'joshspicer';721comment: 'Report when installed extensions are no longer available in the gallery';722queriedIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions queried as potentially missing from gallery' };723missingIds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extensions determined missing from gallery' };724};725type MissingFromGalleryEvent = {726readonly queriedIds: TelemetryTrustedValue<string>;727readonly missingIds: TelemetryTrustedValue<string>;728};729this.telemetryService.publicLog2<MissingFromGalleryEvent, MissingFromGalleryClassification>('extensions:missingFromGallery', {730queriedIds: new TelemetryTrustedValue(queriedIds.join(';')),731missingIds: new TelemetryTrustedValue(missingIds.join(';'))732});733}734}735}736737private async mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise<[Extension, IGalleryExtension][]> {738const mappedExtensions = this.mapInstalledExtensionWithGalleryExtension(galleryExtensions);739const targetPlatform = await this.server.extensionManagementService.getTargetPlatform();740const compatibleGalleryExtensions: IGalleryExtension[] = [];741const compatibleGalleryExtensionsToFetch: IExtensionInfo[] = [];742await Promise.allSettled(mappedExtensions.map(async ([extension, gallery]) => {743if (extension.local) {744if (await this.galleryService.isExtensionCompatible(gallery, extension.local.preRelease, targetPlatform, productVersion)) {745compatibleGalleryExtensions.push(gallery);746} else {747compatibleGalleryExtensionsToFetch.push({ ...extension.local.identifier, preRelease: extension.local.preRelease });748}749}750}));751if (compatibleGalleryExtensionsToFetch.length) {752const result = await this.galleryService.getExtensions(compatibleGalleryExtensionsToFetch, { targetPlatform, compatible: true, queryAllVersions: true, productVersion }, CancellationToken.None);753compatibleGalleryExtensions.push(...result);754}755return this.mapInstalledExtensionWithGalleryExtension(compatibleGalleryExtensions);756}757758private mapInstalledExtensionWithGalleryExtension(galleryExtensions: IGalleryExtension[]): [Extension, IGalleryExtension][] {759const mappedExtensions: [Extension, IGalleryExtension][] = [];760const byUUID = new Map<string, IGalleryExtension>(), byID = new Map<string, IGalleryExtension>();761for (const gallery of galleryExtensions) {762byUUID.set(gallery.identifier.uuid, gallery);763byID.set(gallery.identifier.id.toLowerCase(), gallery);764}765for (const installed of this.installed) {766if (installed.uuid) {767const gallery = byUUID.get(installed.uuid);768if (gallery) {769mappedExtensions.push([installed, gallery]);770continue;771}772}773if (installed.local?.source !== 'resource') {774const gallery = byID.get(installed.identifier.id.toLowerCase());775if (gallery) {776mappedExtensions.push([installed, gallery]);777}778}779}780return mappedExtensions;781}782783private async updateMetadata(localExtension: ILocalExtension, gallery: IGalleryExtension): Promise<ILocalExtension> {784let isPreReleaseVersion = false;785if (localExtension.manifest.version !== gallery.version) {786type GalleryServiceMatchInstalledExtensionClassification = {787owner: 'sandy081';788comment: 'Report when a request is made to update metadata of an installed extension';789};790this.telemetryService.publicLog2<{}, GalleryServiceMatchInstalledExtensionClassification>('galleryService:updateMetadata');791const galleryWithLocalVersion: IGalleryExtension | undefined = (await this.galleryService.getExtensions([{ ...localExtension.identifier, version: localExtension.manifest.version }], CancellationToken.None))[0];792isPreReleaseVersion = !!galleryWithLocalVersion?.properties?.isPreReleaseVersion;793}794return this.workbenchExtensionManagementService.updateMetadata(localExtension, { id: gallery.identifier.uuid, publisherDisplayName: gallery.publisherDisplayName, publisherId: gallery.publisherId, isPreReleaseVersion });795}796797canInstall(galleryExtension: IGalleryExtension): Promise<true | IMarkdownString> {798return this.server.extensionManagementService.canInstall(galleryExtension);799}800801private onInstallExtension(event: InstallExtensionEvent): void {802const { source } = event;803if (source && !URI.isUri(source)) {804const extension = this.installed.find(e => areSameExtensions(e.identifier, source.identifier))805?? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, undefined, source, undefined);806this.installing.push(extension);807this._onChange.fire({ extension });808}809}810811private async fetchInstalledExtensions(productVersion?: IProductVersion): Promise<void> {812const extensionsControlManifest = await this.server.extensionManagementService.getExtensionsControlManifest();813const all = await this.server.extensionManagementService.getInstalled(undefined, undefined, productVersion);814if (this.isWorkspaceServer) {815all.push(...await this.workbenchExtensionManagementService.getInstalledWorkspaceExtensions(true));816}817818// dedup workspace, user and system extensions by giving priority to workspace first and then to user extension.819const installed = groupByExtension(all, r => r.identifier).reduce((result, extensions) => {820if (extensions.length === 1) {821result.push(extensions[0]);822} else {823let workspaceExtension: ILocalExtension | undefined,824userExtension: ILocalExtension | undefined,825systemExtension: ILocalExtension | undefined;826for (const extension of extensions) {827if (extension.isWorkspaceScoped) {828workspaceExtension = extension;829} else if (extension.type === ExtensionType.User) {830userExtension = extension;831} else {832systemExtension = extension;833}834}835const extension = workspaceExtension ?? userExtension ?? systemExtension;836if (extension) {837result.push(extension);838}839}840return result;841}, []);842843const byId = index(this.installed, e => e.local ? e.local.identifier.id : e.identifier.id);844this.installed = installed.map(local => {845const extension = byId[local.identifier.id] || this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined);846extension.local = local;847extension.enablementState = this.extensionEnablementService.getEnablementState(local);848extension.setExtensionsControlManifest(extensionsControlManifest);849return extension;850});851}852853private async reset(): Promise<void> {854this.installed = [];855this.installing = [];856this.uninstalling = [];857await this.fetchInstalledExtensions();858this._onReset.fire();859}860861private async onDidInstallExtensions(results: readonly InstallExtensionResult[]): Promise<void> {862const extensions: Extension[] = [];863for (const event of results) {864const { local, source } = event;865const gallery = source && !URI.isUri(source) ? source : undefined;866const location = source && URI.isUri(source) ? source : undefined;867const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] : null;868this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing;869870let extension: Extension | undefined = installingExtension ? installingExtension871: (location || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined)872: undefined;873if (extension) {874if (local) {875const installed = this.installed.filter(e => areSameExtensions(e.identifier, extension!.identifier))[0];876if (installed) {877extension = installed;878} else {879this.installed.push(extension);880}881extension.local = local;882if (!extension.gallery) {883extension.gallery = gallery;884}885extension.enablementState = this.extensionEnablementService.getEnablementState(local);886}887extensions.push(extension);888}889this._onChange.fire(!local || !extension ? undefined : { extension, operation: event.operation });890}891892if (extensions.length) {893const manifest = await this.server.extensionManagementService.getExtensionsControlManifest();894for (const extension of extensions) {895extension.setExtensionsControlManifest(manifest);896}897this.matchInstalledExtensionsWithGallery(extensions);898}899}900901private async onDidUpdateExtensionMetadata(local: ILocalExtension): Promise<void> {902const extension = this.installed.find(e => areSameExtensions(e.identifier, local.identifier));903if (extension?.local) {904extension.local = local;905this._onChange.fire({ extension });906}907}908909private async matchInstalledExtensionsWithGallery(extensions: Extension[]): Promise<void> {910const toMatch = extensions.filter(e => e.local && !e.gallery && e.local.source !== 'resource');911if (!toMatch.length) {912return;913}914if (!this.galleryService.isEnabled()) {915return;916}917const galleryExtensions = await this.galleryService.getExtensions(toMatch.map(e => ({ ...e.identifier, preRelease: e.local?.preRelease })), { compatible: true, targetPlatform: await this.server.extensionManagementService.getTargetPlatform() }, CancellationToken.None);918for (const extension of extensions) {919const compatible = galleryExtensions.find(e => areSameExtensions(e.identifier, extension.identifier));920if (compatible) {921extension.gallery = compatible;922this._onChange.fire({ extension });923}924}925}926927private onUninstallExtension(identifier: IExtensionIdentifier): void {928const extension = this.installed.filter(e => areSameExtensions(e.identifier, identifier))[0];929if (extension) {930const uninstalling = this.uninstalling.filter(e => areSameExtensions(e.identifier, identifier))[0] || extension;931this.uninstalling = [uninstalling, ...this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier))];932this._onChange.fire(uninstalling ? { extension: uninstalling } : undefined);933}934}935936private onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {937const uninstalled = this.uninstalling.find(e => areSameExtensions(e.identifier, identifier)) || this.installed.find(e => areSameExtensions(e.identifier, identifier));938this.uninstalling = this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier));939if (!error) {940this.installed = this.installed.filter(e => !areSameExtensions(e.identifier, identifier));941}942if (uninstalled) {943this._onChange.fire({ extension: uninstalled });944}945}946947private onEnablementChanged(platformExtensions: readonly IPlatformExtension[]) {948const extensions = this.local.filter(e => platformExtensions.some(p => areSameExtensions(e.identifier, p.identifier)));949for (const extension of extensions) {950if (extension.local) {951const enablementState = this.extensionEnablementService.getEnablementState(extension.local);952if (enablementState !== extension.enablementState) {953extension.enablementState = enablementState;954this._onChange.fire({ extension });955}956}957}958}959960getExtensionState(extension: Extension): ExtensionState {961if (extension.gallery && this.installing.some(e => !!e.gallery && areSameExtensions(e.gallery.identifier, extension.gallery!.identifier))) {962return ExtensionState.Installing;963}964if (this.uninstalling.some(e => areSameExtensions(e.identifier, extension.identifier))) {965return ExtensionState.Uninstalling;966}967const local = this.installed.filter(e => e === extension || (e.gallery && extension.gallery && areSameExtensions(e.gallery.identifier, extension.gallery.identifier)))[0];968return local ? ExtensionState.Installed : ExtensionState.Uninstalled;969}970}971972export class ExtensionsWorkbenchService extends Disposable implements IExtensionsWorkbenchService, IURLHandler {973974private static readonly UpdatesCheckInterval = 1000 * 60 * 60 * 12; // 12 hours975declare readonly _serviceBrand: undefined;976977private hasOutdatedExtensionsContextKey: IContextKey<boolean>;978979private readonly localExtensions: Extensions | null = null;980private readonly remoteExtensions: Extensions | null = null;981private readonly webExtensions: Extensions | null = null;982private readonly extensionsServers: Extensions[] = [];983984private updatesCheckDelayer: ThrottledDelayer<void>;985private autoUpdateDelayer: ThrottledDelayer<void>;986987private readonly _onChange = this._register(new Emitter<IExtension | undefined>());988get onChange(): Event<IExtension | undefined> { return this._onChange.event; }989990private extensionsNotification: IExtensionsNotification & { readonly key: string } | undefined;991private readonly _onDidChangeExtensionsNotification = new Emitter<IExtensionsNotification | undefined>();992readonly onDidChangeExtensionsNotification = this._onDidChangeExtensionsNotification.event;993994private readonly _onReset = new Emitter<void>();995get onReset() { return this._onReset.event; }996997private installing: IExtension[] = [];998private tasksInProgress: CancelablePromise<any>[] = [];9991000readonly whenInitialized: Promise<void>;10011002constructor(1003@IInstantiationService private readonly instantiationService: IInstantiationService,1004@IEditorService private readonly editorService: IEditorService,1005@IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService,1006@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,1007@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService,1008@IConfigurationService private readonly configurationService: IConfigurationService,1009@ITelemetryService private readonly telemetryService: ITelemetryService,1010@INotificationService private readonly notificationService: INotificationService,1011@IURLService urlService: IURLService,1012@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,1013@IHostService private readonly hostService: IHostService,1014@IProgressService private readonly progressService: IProgressService,1015@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,1016@ILanguageService private readonly languageService: ILanguageService,1017@IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService,1018@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,1019@IProductService private readonly productService: IProductService,1020@IContextKeyService contextKeyService: IContextKeyService,1021@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,1022@ILogService private readonly logService: ILogService,1023@IExtensionService private readonly extensionService: IExtensionService,1024@ILocaleService private readonly localeService: ILocaleService,1025@ILifecycleService private readonly lifecycleService: ILifecycleService,1026@IFileService private readonly fileService: IFileService,1027@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,1028@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,1029@IStorageService private readonly storageService: IStorageService,1030@IDialogService private readonly dialogService: IDialogService,1031@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,1032@IUpdateService private readonly updateService: IUpdateService,1033@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,1034@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,1035@IViewsService private readonly viewsService: IViewsService,1036@IFileDialogService private readonly fileDialogService: IFileDialogService,1037@IQuickInputService private readonly quickInputService: IQuickInputService,1038@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,1039) {1040super();10411042this.hasOutdatedExtensionsContextKey = HasOutdatedExtensionsContext.bindTo(contextKeyService);1043if (extensionManagementServerService.localExtensionManagementServer) {1044this.localExtensions = this._register(instantiationService.createInstance(Extensions,1045extensionManagementServerService.localExtensionManagementServer,1046ext => this.getExtensionState(ext),1047ext => this.getRuntimeState(ext),1048!extensionManagementServerService.remoteExtensionManagementServer1049));1050this._register(this.localExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));1051this._register(this.localExtensions.onReset(e => this.reset()));1052this.extensionsServers.push(this.localExtensions);1053}1054if (extensionManagementServerService.remoteExtensionManagementServer) {1055this.remoteExtensions = this._register(instantiationService.createInstance(Extensions,1056extensionManagementServerService.remoteExtensionManagementServer,1057ext => this.getExtensionState(ext),1058ext => this.getRuntimeState(ext),1059true1060));1061this._register(this.remoteExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));1062this._register(this.remoteExtensions.onReset(e => this.reset()));1063this.extensionsServers.push(this.remoteExtensions);1064}1065if (extensionManagementServerService.webExtensionManagementServer) {1066this.webExtensions = this._register(instantiationService.createInstance(Extensions,1067extensionManagementServerService.webExtensionManagementServer,1068ext => this.getExtensionState(ext),1069ext => this.getRuntimeState(ext),1070!(extensionManagementServerService.remoteExtensionManagementServer || extensionManagementServerService.localExtensionManagementServer)1071));1072this._register(this.webExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));1073this._register(this.webExtensions.onReset(e => this.reset()));1074this.extensionsServers.push(this.webExtensions);1075}10761077this.updatesCheckDelayer = new ThrottledDelayer<void>(ExtensionsWorkbenchService.UpdatesCheckInterval);1078this.autoUpdateDelayer = new ThrottledDelayer<void>(1000);1079this._register(toDisposable(() => {1080this.updatesCheckDelayer.cancel();1081this.autoUpdateDelayer.cancel();1082}));10831084urlService.registerHandler(this);10851086this.whenInitialized = this.initialize();1087}10881089private async initialize(): Promise<void> {1090// initialize local extensions1091await Promise.all([this.queryLocal(), this.extensionService.whenInstalledExtensionsRegistered()]);1092if (this._store.isDisposed) {1093return;1094}1095this.onDidChangeRunningExtensions(this.extensionService.extensions, []);1096this._register(this.extensionService.onDidChangeExtensions(({ added, removed }) => this.onDidChangeRunningExtensions(added, removed)));10971098await this.lifecycleService.when(LifecyclePhase.Eventually);1099if (this._store.isDisposed) {1100return;1101}11021103this.initializeAutoUpdate();1104this.updateExtensionsNotificaiton();1105this.reportInstalledExtensionsTelemetry();1106this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, this._store)(e => this.onDidDismissedNotificationsValueChange()));1107this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange()));1108this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_DONOT_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange()));1109this._register(Event.debounce(this.onChange, () => undefined, 100)(() => {1110this.updateExtensionsNotificaiton();1111this.reportProgressFromOtherSources();1112}));1113}11141115private initializeAutoUpdate(): void {1116// Register listeners for auto updates1117this._register(this.configurationService.onDidChangeConfiguration(e => {1118if (e.affectsConfiguration(AutoUpdateConfigurationKey)) {1119if (this.isAutoUpdateEnabled()) {1120this.eventuallyAutoUpdateExtensions();1121}1122}1123if (e.affectsConfiguration(AutoCheckUpdatesConfigurationKey)) {1124if (this.isAutoCheckUpdatesEnabled()) {1125this.checkForUpdates(`Enabled auto check updates`);1126}1127}1128}));1129this._register(this.extensionEnablementService.onEnablementChanged(platformExtensions => {1130if (this.getAutoUpdateValue() === 'onlyEnabledExtensions' && platformExtensions.some(e => this.extensionEnablementService.isEnabled(e))) {1131this.checkForUpdates('Extension enablement changed');1132}1133}));1134this._register(Event.debounce(this.onChange, () => undefined, 100)(() => this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0)));1135this._register(this.updateService.onStateChange(e => {1136if ((e.type === StateType.CheckingForUpdates && e.explicit) || e.type === StateType.AvailableForDownload || e.type === StateType.Downloaded) {1137this.telemetryService.publicLog2<{}, {1138owner: 'sandy081';1139comment: 'Report when update check is triggered on product update';1140}>('extensions:updatecheckonproductupdate');1141if (this.isAutoCheckUpdatesEnabled()) {1142this.checkForUpdates('Product update');1143}1144}1145}));11461147this._register(this.allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => {1148if (this.isAutoCheckUpdatesEnabled()) {1149this.checkForUpdates('Allowed extensions changed');1150}1151}));11521153// Update AutoUpdate Contexts1154this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0);11551156// Check for updates1157this.eventuallyCheckForUpdates(true);11581159if (isWeb) {1160this.syncPinnedBuiltinExtensions();1161// Always auto update builtin extensions in web1162if (!this.isAutoUpdateEnabled()) {1163this.autoUpdateBuiltinExtensions();1164}1165}11661167this.registerAutoRestartListener();1168this._register(this.configurationService.onDidChangeConfiguration(e => {1169if (e.affectsConfiguration(AutoRestartConfigurationKey)) {1170this.registerAutoRestartListener();1171}1172}));1173}11741175private isAutoUpdateEnabled(): boolean {1176return this.getAutoUpdateValue() !== false;1177}11781179getAutoUpdateValue(): AutoUpdateConfigurationValue {1180const autoUpdate = this.configurationService.getValue<AutoUpdateConfigurationValue>(AutoUpdateConfigurationKey);1181if (<any>autoUpdate === 'onlySelectedExtensions') {1182return false;1183}1184return isBoolean(autoUpdate) || autoUpdate === 'onlyEnabledExtensions' ? autoUpdate : true;1185}11861187async updateAutoUpdateForAllExtensions(isAutoUpdateEnabled: boolean): Promise<void> {1188const wasAutoUpdateEnabled = this.isAutoUpdateEnabled();1189if (wasAutoUpdateEnabled === isAutoUpdateEnabled) {1190return;1191}11921193const result = await this.dialogService.confirm({1194title: nls.localize('confirmEnableDisableAutoUpdate', "Auto Update Extensions"),1195message: isAutoUpdateEnabled1196? nls.localize('confirmEnableAutoUpdate', "Do you want to enable auto update for all extensions?")1197: nls.localize('confirmDisableAutoUpdate', "Do you want to disable auto update for all extensions?"),1198detail: nls.localize('confirmEnableDisableAutoUpdateDetail', "This will reset any auto update settings you have set for individual extensions."),1199});1200if (!result.confirmed) {1201return;1202}12031204// Reset extensions enabled for auto update first to prevent them from being updated1205this.setEnabledAutoUpdateExtensions([]);12061207await this.configurationService.updateValue(AutoUpdateConfigurationKey, isAutoUpdateEnabled);12081209this.setDisabledAutoUpdateExtensions([]);1210await this.updateExtensionsPinnedState(!isAutoUpdateEnabled);1211this._onChange.fire(undefined);1212}12131214private readonly autoRestartListenerDisposable = this._register(new MutableDisposable());1215private registerAutoRestartListener(): void {1216this.autoRestartListenerDisposable.value = undefined;1217if (this.configurationService.getValue(AutoRestartConfigurationKey) === true) {1218this.autoRestartListenerDisposable.value = this.hostService.onDidChangeFocus(focus => {1219if (!focus && this.configurationService.getValue(AutoRestartConfigurationKey) === true) {1220this.updateRunningExtensions(undefined, true);1221}1222});1223}1224}12251226private reportInstalledExtensionsTelemetry() {1227const extensionIds = this.installed.filter(extension =>1228!extension.isBuiltin &&1229(extension.enablementState === EnablementState.EnabledWorkspace ||1230extension.enablementState === EnablementState.EnabledGlobally))1231.map(extension => ExtensionIdentifier.toKey(extension.identifier.id));1232this.telemetryService.publicLog2<InstalledExtensionsEvent, ExtensionsLoadClassification>('installedExtensions', { extensionIds: new TelemetryTrustedValue(extensionIds.join(';')), count: extensionIds.length });1233}12341235private async onDidChangeRunningExtensions(added: ReadonlyArray<IExtensionDescription>, removed: ReadonlyArray<IExtensionDescription>): Promise<void> {1236const changedExtensions: IExtension[] = [];1237const extensionsToFetch: IExtensionDescription[] = [];1238for (const desc of added) {1239const extension = this.installed.find(e => areSameExtensions({ id: desc.identifier.value, uuid: desc.uuid }, e.identifier));1240if (extension) {1241changedExtensions.push(extension);1242} else {1243extensionsToFetch.push(desc);1244}1245}1246const workspaceExtensions: IExtensionDescription[] = [];1247for (const desc of removed) {1248if (this.workspaceContextService.isInsideWorkspace(desc.extensionLocation)) {1249workspaceExtensions.push(desc);1250} else {1251extensionsToFetch.push(desc);1252}1253}1254if (extensionsToFetch.length) {1255const extensions = await this.getExtensions(extensionsToFetch.map(e => ({ id: e.identifier.value, uuid: e.uuid })), CancellationToken.None);1256changedExtensions.push(...extensions);1257}1258if (workspaceExtensions.length) {1259const extensions = await this.getResourceExtensions(workspaceExtensions.map(e => e.extensionLocation), true);1260changedExtensions.push(...extensions);1261}1262for (const changedExtension of changedExtensions) {1263this._onChange.fire(changedExtension);1264}1265}12661267private updateExtensionsPinnedState(pinned: boolean): Promise<void> {1268return this.progressService.withProgress({1269location: ProgressLocation.Extensions,1270title: nls.localize('updatingExtensions', "Updating Extensions Auto Update State"),1271}, () => this.extensionManagementService.resetPinnedStateForAllUserExtensions(pinned));1272}12731274private reset(): void {1275for (const task of this.tasksInProgress) {1276task.cancel();1277}1278this.tasksInProgress = [];1279this.installing = [];1280this.onDidChangeExtensions();1281this._onReset.fire();1282}12831284private onDidChangeExtensions(extension?: IExtension): void {1285this._installed = undefined;1286this._local = undefined;1287this._onChange.fire(extension);1288}12891290private _local: IExtension[] | undefined;1291get local(): IExtension[] {1292if (!this._local) {1293if (this.extensionsServers.length === 1) {1294this._local = this.installed;1295} else {1296this._local = [];1297const byId = groupByExtension(this.installed, r => r.identifier);1298for (const extensions of byId) {1299this._local.push(this.getPrimaryExtension(extensions));1300}1301}1302}1303return this._local;1304}13051306private _installed: IExtension[] | undefined;1307get installed(): IExtension[] {1308if (!this._installed) {1309this._installed = [];1310for (const extensions of this.extensionsServers) {1311for (const extension of extensions.local) {1312this._installed.push(extension);1313}1314}1315}1316return this._installed;1317}13181319get outdated(): IExtension[] {1320return this.installed.filter(e => e.outdated && e.local && e.state === ExtensionState.Installed);1321}13221323async queryLocal(server?: IExtensionManagementServer): Promise<IExtension[]> {1324if (server) {1325if (this.localExtensions && this.extensionManagementServerService.localExtensionManagementServer === server) {1326return this.localExtensions.queryInstalled(this.getProductVersion());1327}1328if (this.remoteExtensions && this.extensionManagementServerService.remoteExtensionManagementServer === server) {1329return this.remoteExtensions.queryInstalled(this.getProductVersion());1330}1331if (this.webExtensions && this.extensionManagementServerService.webExtensionManagementServer === server) {1332return this.webExtensions.queryInstalled(this.getProductVersion());1333}1334}13351336if (this.localExtensions) {1337try {1338await this.localExtensions.queryInstalled(this.getProductVersion());1339}1340catch (error) {1341this.logService.error(error);1342}1343}1344if (this.remoteExtensions) {1345try {1346await this.remoteExtensions.queryInstalled(this.getProductVersion());1347}1348catch (error) {1349this.logService.error(error);1350}1351}1352if (this.webExtensions) {1353try {1354await this.webExtensions.queryInstalled(this.getProductVersion());1355}1356catch (error) {1357this.logService.error(error);1358}1359}1360return this.local;1361}13621363queryGallery(token: CancellationToken): Promise<IPager<IExtension>>;1364queryGallery(options: IQueryOptions, token: CancellationToken): Promise<IPager<IExtension>>;1365async queryGallery(arg1: any, arg2?: any): Promise<IPager<IExtension>> {1366if (!this.galleryService.isEnabled()) {1367return singlePagePager([]);1368}13691370const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1;1371const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2;1372options.text = options.text ? this.resolveQueryText(options.text) : options.text;1373options.includePreRelease = isUndefined(options.includePreRelease) ? this.extensionManagementService.preferPreReleases : options.includePreRelease;13741375const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();1376const pager = await this.galleryService.query(options, token);1377this.syncInstalledExtensionsWithGallery(pager.firstPage);1378return {1379firstPage: pager.firstPage.map(gallery => this.fromGallery(gallery, extensionsControlManifest)),1380total: pager.total,1381pageSize: pager.pageSize,1382getPage: async (pageIndex, token) => {1383const page = await pager.getPage(pageIndex, token);1384this.syncInstalledExtensionsWithGallery(page);1385return page.map(gallery => this.fromGallery(gallery, extensionsControlManifest));1386}1387};1388}13891390getExtensions(extensionInfos: IExtensionInfo[], token: CancellationToken): Promise<IExtension[]>;1391getExtensions(extensionInfos: IExtensionInfo[], options: IExtensionQueryOptions, token: CancellationToken): Promise<IExtension[]>;1392async getExtensions(extensionInfos: IExtensionInfo[], arg1: any, arg2?: any): Promise<IExtension[]> {1393if (!this.galleryService.isEnabled()) {1394return [];1395}13961397extensionInfos.forEach(e => e.preRelease = e.preRelease ?? this.extensionManagementService.preferPreReleases);1398const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();1399const galleryExtensions = await this.galleryService.getExtensions(extensionInfos, arg1, arg2);1400this.syncInstalledExtensionsWithGallery(galleryExtensions);1401return galleryExtensions.map(gallery => this.fromGallery(gallery, extensionsControlManifest));1402}14031404async getResourceExtensions(locations: URI[], isWorkspaceScoped: boolean): Promise<IExtension[]> {1405const resourceExtensions = await this.extensionManagementService.getExtensions(locations);1406return resourceExtensions.map(resourceExtension => this.getInstalledExtensionMatchingLocation(resourceExtension.location)1407?? this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, undefined, { resourceExtension, isWorkspaceScoped }));1408}14091410private onDidDismissedNotificationsValueChange(): void {1411if (1412this.dismissedNotificationsValue !== this.getDismissedNotificationsValue() /* This checks if current window changed the value or not */1413) {1414this._dismissedNotificationsValue = undefined;1415this.updateExtensionsNotificaiton();1416}1417}14181419private updateExtensionsNotificaiton(): void {1420const computedNotificiations = this.computeExtensionsNotifications();1421const dismissedNotifications: string[] = [];14221423let extensionsNotification: IExtensionsNotification & { key: string } | undefined;1424if (computedNotificiations.length) {1425// populate dismissed notifications with the ones that are still valid1426for (const dismissedNotification of this.getDismissedNotifications()) {1427if (computedNotificiations.some(e => e.key === dismissedNotification)) {1428dismissedNotifications.push(dismissedNotification);1429}1430}1431if (!dismissedNotifications.includes(computedNotificiations[0].key)) {1432extensionsNotification = {1433message: computedNotificiations[0].message,1434severity: computedNotificiations[0].severity,1435extensions: computedNotificiations[0].extensions,1436key: computedNotificiations[0].key,1437dismiss: () => {1438this.setDismissedNotifications([...this.getDismissedNotifications(), computedNotificiations[0].key]);1439this.updateExtensionsNotificaiton();1440},1441};1442}1443}1444this.setDismissedNotifications(dismissedNotifications);14451446if (this.extensionsNotification?.key !== extensionsNotification?.key) {1447this.extensionsNotification = extensionsNotification;1448this._onDidChangeExtensionsNotification.fire(this.extensionsNotification);1449}1450}14511452private computeExtensionsNotifications(): Array<Omit<IExtensionsNotification, 'dismiss'> & { key: string }> {1453const computedNotificiations: Array<Omit<IExtensionsNotification, 'dismiss'> & { key: string }> = [];14541455const disallowedExtensions = this.local.filter(e => e.enablementState === EnablementState.DisabledByAllowlist);1456if (disallowedExtensions.length) {1457computedNotificiations.push({1458message: this.configurationService.inspect(AllowedExtensionsConfigKey).policy1459? nls.localize('disallowed extensions by policy', "Some extensions are disabled because they are not allowed by your system administrator.")1460: nls.localize('disallowed extensions', "Some extensions are disabled because they are configured not to be allowed."),1461severity: Severity.Warning,1462extensions: disallowedExtensions,1463key: 'disallowedExtensions:' + disallowedExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => e.identifier.id.toLowerCase()).join('-'),1464});1465}14661467const invalidExtensions = this.local.filter(e => e.enablementState === EnablementState.DisabledByInvalidExtension && !e.isWorkspaceScoped);1468if (invalidExtensions.length) {1469if (invalidExtensions.some(e => e.local && e.local.manifest.engines?.vscode &&1470(!isEngineValid(e.local.manifest.engines.vscode, this.productService.version, this.productService.date) || areApiProposalsCompatible([...e.local.manifest.enabledApiProposals ?? []]))1471)) {1472computedNotificiations.push({1473message: nls.localize('incompatibleExtensions', "Some extensions are disabled due to version incompatibility. Review and update them."),1474severity: Severity.Warning,1475extensions: invalidExtensions,1476key: 'incompatibleExtensions:' + invalidExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => `${e.identifier.id.toLowerCase()}@${e.local?.manifest.version}`).join('-'),1477});1478} else {1479computedNotificiations.push({1480message: nls.localize('invalidExtensions', "Invalid extensions detected. Review them."),1481severity: Severity.Warning,1482extensions: invalidExtensions,1483key: 'invalidExtensions:' + invalidExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => `${e.identifier.id.toLowerCase()}@${e.local?.manifest.version}`).join('-'),1484});1485}1486}14871488const deprecatedExtensions = this.local.filter(e => !!e.deprecationInfo && e.local && this.extensionEnablementService.isEnabled(e.local));1489if (deprecatedExtensions.length) {1490computedNotificiations.push({1491message: nls.localize('deprecated extensions', "Deprecated extensions detected. Review them and migrate to alternatives."),1492severity: Severity.Warning,1493extensions: deprecatedExtensions,1494key: 'deprecatedExtensions:' + deprecatedExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => e.identifier.id.toLowerCase()).join('-'),1495});1496}14971498return computedNotificiations;1499}15001501getExtensionsNotification(): IExtensionsNotification | undefined {1502return this.extensionsNotification;1503}15041505private resolveQueryText(text: string): string {1506text = text.replace(/@web/g, `tag:"${WEB_EXTENSION_TAG}"`);15071508const extensionRegex = /\bext:([^\s]+)\b/g;1509if (extensionRegex.test(text)) {1510text = text.replace(extensionRegex, (m, ext) => {15111512// Get curated keywords1513const lookup = this.productService.extensionKeywords || {};1514const keywords = lookup[ext] || [];15151516// Get mode name1517const languageId = this.languageService.guessLanguageIdByFilepathOrFirstLine(URI.file(`.${ext}`));1518const languageName = languageId && this.languageService.getLanguageName(languageId);1519const languageTag = languageName ? ` tag:"${languageName}"` : '';15201521// Construct a rich query1522return `tag:"__ext_${ext}" tag:"__ext_.${ext}" ${keywords.map(tag => `tag:"${tag}"`).join(' ')}${languageTag} tag:"${ext}"`;1523});1524}1525return text.substr(0, 350);1526}15271528private fromGallery(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): IExtension {1529let extension = this.getInstalledExtensionMatchingGallery(gallery);1530if (!extension) {1531extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined);1532(<Extension>extension).setExtensionsControlManifest(extensionsControlManifest);1533}1534return extension;1535}15361537private getInstalledExtensionMatchingGallery(gallery: IGalleryExtension): IExtension | null {1538for (const installed of this.local) {1539if (installed.identifier.uuid) { // Installed from Gallery1540if (installed.identifier.uuid === gallery.identifier.uuid) {1541return installed;1542}1543} else if (installed.local?.source !== 'resource') {1544if (areSameExtensions(installed.identifier, gallery.identifier)) { // Installed from other sources1545return installed;1546}1547}1548}1549return null;1550}15511552private getInstalledExtensionMatchingLocation(location: URI): IExtension | null {1553return this.local.find(e => e.local && this.uriIdentityService.extUri.isEqualOrParent(location, e.local?.location)) ?? null;1554}15551556async open(extension: IExtension | string, options?: IExtensionEditorOptions): Promise<void> {1557if (typeof extension === 'string') {1558const id = extension;1559extension = this.installed.find(e => areSameExtensions(e.identifier, { id })) ?? (await this.getExtensions([{ id: extension }], CancellationToken.None))[0];1560}1561if (!extension) {1562throw new Error(`Extension not found. ${extension}`);1563}1564await this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), options, options?.sideByside ? SIDE_GROUP : ACTIVE_GROUP);1565}15661567async openSearch(searchValue: string, preserveFoucs?: boolean): Promise<void> {1568const viewPaneContainer = (await this.viewsService.openViewContainer(VIEWLET_ID, true))?.getViewPaneContainer() as IExtensionsViewPaneContainer;1569viewPaneContainer.search(searchValue);1570if (!preserveFoucs) {1571viewPaneContainer.focus();1572}1573}15741575getExtensionRuntimeStatus(extension: IExtension): IExtensionRuntimeStatus | undefined {1576const extensionsStatus = this.extensionService.getExtensionsStatus();1577for (const id of Object.keys(extensionsStatus)) {1578if (areSameExtensions({ id }, extension.identifier)) {1579return extensionsStatus[id];1580}1581}1582return undefined;1583}15841585async updateRunningExtensions(message = nls.localize('restart', "Changing extension enablement"), auto: boolean = false): Promise<void> {1586const toAdd: ILocalExtension[] = [];1587const toRemove: string[] = [];15881589const extensionsToCheck = [...this.local];1590for (const extension of extensionsToCheck) {1591const runtimeState = extension.runtimeState;1592if (!runtimeState || runtimeState.action !== ExtensionRuntimeActionType.RestartExtensions) {1593continue;1594}1595if (extension.state === ExtensionState.Uninstalled) {1596toRemove.push(extension.identifier.id);1597continue;1598}1599if (!extension.local) {1600continue;1601}1602const isEnabled = this.extensionEnablementService.isEnabled(extension.local);1603if (isEnabled) {1604const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier));1605if (runningExtension) {1606toRemove.push(runningExtension.identifier.value);1607}1608toAdd.push(extension.local);1609} else {1610toRemove.push(extension.identifier.id);1611}1612}16131614for (const extension of this.extensionService.extensions) {1615if (extension.isUnderDevelopment) {1616continue;1617}1618if (extensionsToCheck.some(e => areSameExtensions({ id: extension.identifier.value, uuid: extension.uuid }, e.local?.identifier ?? e.identifier))) {1619continue;1620}1621// Extension is running but doesn't exist locally. Remove it from running extensions.1622toRemove.push(extension.identifier.value);1623}16241625if (toAdd.length || toRemove.length) {1626if (await this.extensionService.stopExtensionHosts(message, auto)) {1627await this.extensionService.startExtensionHosts({ toAdd, toRemove });1628if (auto) {1629this.notificationService.notify({1630severity: Severity.Info,1631message: nls.localize('extensionsAutoRestart', "Extensions were auto restarted to enable updates."),1632priority: NotificationPriority.SILENT1633});1634}1635type ExtensionsAutoRestartClassification = {1636owner: 'sandy081';1637comment: 'Report when extensions are auto restarted';1638count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions auto restarted' };1639auto: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the restart was triggered automatically' };1640};1641type ExtensionsAutoRestartEvent = {1642count: number;1643auto: boolean;1644};1645this.telemetryService.publicLog2<ExtensionsAutoRestartEvent, ExtensionsAutoRestartClassification>('extensions:autorestart', { count: toAdd.length + toRemove.length, auto });1646}1647}1648}16491650private getRuntimeState(extension: IExtension): ExtensionRuntimeState | undefined {1651const isUninstalled = extension.state === ExtensionState.Uninstalled;1652const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value }, extension.identifier));1653const reloadAction = this.extensionManagementServerService.remoteExtensionManagementServer ? ExtensionRuntimeActionType.ReloadWindow : ExtensionRuntimeActionType.RestartExtensions;1654const reloadActionLabel = reloadAction === ExtensionRuntimeActionType.ReloadWindow ? nls.localize('reload', "reload window") : nls.localize('restart extensions', "restart extensions");16551656if (isUninstalled) {1657const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension);1658const isSameExtensionRunning = runningExtension1659&& (!extension.server || extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)))1660&& (!extension.resourceExtension || this.uriIdentityService.extUri.isEqual(extension.resourceExtension.location, runningExtension.extensionLocation));1661if (!canRemoveRunningExtension && isSameExtensionRunning && !runningExtension.isUnderDevelopment) {1662return { action: reloadAction, reason: nls.localize('postUninstallTooltip', "Please {0} to complete the uninstallation of this extension.", reloadActionLabel) };1663}1664return undefined;1665}1666if (extension.local) {1667const isSameExtensionRunning = runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension));1668const isEnabled = this.extensionEnablementService.isEnabled(extension.local);16691670// Extension is running1671if (runningExtension) {1672if (isEnabled) {1673// No Reload is required if extension can run without reload1674if (this.extensionService.canAddExtension(toExtensionDescription(extension.local))) {1675return undefined;1676}1677const runningExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension));16781679if (isSameExtensionRunning) {1680// Different version or target platform of same extension is running. Requires reload to run the current version1681if (!runningExtension.isUnderDevelopment && (extension.version !== runningExtension.version || extension.local.targetPlatform !== runningExtension.targetPlatform)) {1682const productCurrentVersion = this.getProductCurrentVersion();1683const productUpdateVersion = this.getProductUpdateVersion();1684if (productUpdateVersion1685&& !isEngineValid(extension.local.manifest.engines.vscode, productCurrentVersion.version, productCurrentVersion.date)1686&& isEngineValid(extension.local.manifest.engines.vscode, productUpdateVersion.version, productUpdateVersion.date)1687) {1688const state = this.updateService.state;1689if (state.type === StateType.AvailableForDownload) {1690return { action: ExtensionRuntimeActionType.DownloadUpdate, reason: nls.localize('postUpdateDownloadTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) };1691}1692if (state.type === StateType.Downloaded) {1693return { action: ExtensionRuntimeActionType.ApplyUpdate, reason: nls.localize('postUpdateUpdateTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) };1694}1695if (state.type === StateType.Ready) {1696return { action: ExtensionRuntimeActionType.QuitAndInstall, reason: nls.localize('postUpdateRestartTooltip', "Please restart {0} to enable the updated extension.", this.productService.nameLong) };1697}1698return undefined;1699}1700return { action: reloadAction, reason: nls.localize('postUpdateTooltip', "Please {0} to enable the updated extension.", reloadActionLabel) };1701}17021703if (this.extensionsServers.length > 1) {1704const extensionInOtherServer = this.installed.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server !== extension.server)[0];1705if (extensionInOtherServer) {1706// This extension prefers to run on UI/Local side but is running in remote1707if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.localExtensionManagementServer) {1708return { action: reloadAction, reason: nls.localize('enable locally', "Please {0} to enable this extension locally.", reloadActionLabel) };1709}17101711// This extension prefers to run on Workspace/Remote side but is running in local1712if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.remoteExtensionManagementServer) {1713return { action: reloadAction, reason: nls.localize('enable remote', "Please {0} to enable this extension in {1}.", reloadActionLabel, this.extensionManagementServerService.remoteExtensionManagementServer?.label) };1714}1715}1716}17171718} else {17191720if (extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) {1721// This extension prefers to run on UI/Local side but is running in remote1722if (this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest)) {1723return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };1724}1725}1726if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) {1727// This extension prefers to run on Workspace/Remote side but is running in local1728if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest)) {1729return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };1730}1731}1732}1733return undefined;1734} else {1735if (isSameExtensionRunning) {1736return { action: reloadAction, reason: nls.localize('postDisableTooltip', "Please {0} to disable this extension.", reloadActionLabel) };1737}1738}1739return undefined;1740}17411742// Extension is not running1743else {1744if (isEnabled && !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) {1745return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };1746}17471748const otherServer = extension.server ? extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null;1749if (otherServer && extension.enablementState === EnablementState.DisabledByExtensionKind) {1750const extensionInOtherServer = this.local.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server === otherServer)[0];1751// Same extension in other server exists and1752if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) {1753return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) };1754}1755}1756}1757}1758return undefined;1759}17601761private getPrimaryExtension(extensions: IExtension[]): IExtension {1762if (extensions.length === 1) {1763return extensions[0];1764}17651766const enabledExtensions = extensions.filter(e => e.local && this.extensionEnablementService.isEnabled(e.local));1767if (enabledExtensions.length === 1) {1768return enabledExtensions[0];1769}17701771const extensionsToChoose = enabledExtensions.length ? enabledExtensions : extensions;1772const manifest = extensionsToChoose.find(e => e.local && e.local.manifest)?.local?.manifest;17731774// Manifest is not found which should not happen.1775// In which case return the first extension.1776if (!manifest) {1777return extensionsToChoose[0];1778}17791780const extensionKinds = this.extensionManifestPropertiesService.getExtensionKind(manifest);17811782let extension = extensionsToChoose.find(extension => {1783for (const extensionKind of extensionKinds) {1784switch (extensionKind) {1785case 'ui':1786/* UI extension is chosen only if it is installed locally */1787if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {1788return true;1789}1790return false;1791case 'workspace':1792/* Choose remote workspace extension if exists */1793if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) {1794return true;1795}1796return false;1797case 'web':1798/* Choose web extension if exists */1799if (extension.server === this.extensionManagementServerService.webExtensionManagementServer) {1800return true;1801}1802return false;1803}1804}1805return false;1806});18071808if (!extension && this.extensionManagementServerService.localExtensionManagementServer) {1809extension = extensionsToChoose.find(extension => {1810for (const extensionKind of extensionKinds) {1811switch (extensionKind) {1812case 'workspace':1813/* Choose local workspace extension if exists */1814if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {1815return true;1816}1817return false;1818case 'web':1819/* Choose local web extension if exists */1820if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) {1821return true;1822}1823return false;1824}1825}1826return false;1827});1828}18291830if (!extension && this.extensionManagementServerService.webExtensionManagementServer) {1831extension = extensionsToChoose.find(extension => {1832for (const extensionKind of extensionKinds) {1833switch (extensionKind) {1834case 'web':1835/* Choose web extension if exists */1836if (extension.server === this.extensionManagementServerService.webExtensionManagementServer) {1837return true;1838}1839return false;1840}1841}1842return false;1843});1844}18451846if (!extension && this.extensionManagementServerService.remoteExtensionManagementServer) {1847extension = extensionsToChoose.find(extension => {1848for (const extensionKind of extensionKinds) {1849switch (extensionKind) {1850case 'web':1851/* Choose remote web extension if exists */1852if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) {1853return true;1854}1855return false;1856}1857}1858return false;1859});1860}18611862return extension || extensions[0];1863}18641865private getExtensionState(extension: Extension): ExtensionState {1866if (this.installing.some(i => areSameExtensions(i.identifier, extension.identifier) && (!extension.server || i.server === extension.server))) {1867return ExtensionState.Installing;1868}1869if (this.remoteExtensions) {1870const state = this.remoteExtensions.getExtensionState(extension);1871if (state !== ExtensionState.Uninstalled) {1872return state;1873}1874}1875if (this.webExtensions) {1876const state = this.webExtensions.getExtensionState(extension);1877if (state !== ExtensionState.Uninstalled) {1878return state;1879}1880}1881if (this.localExtensions) {1882return this.localExtensions.getExtensionState(extension);1883}1884return ExtensionState.Uninstalled;1885}18861887async checkForUpdates(reason?: string, onlyBuiltin?: boolean): Promise<void> {1888if (reason) {1889this.logService.trace(`[Extensions]: Checking for updates. Reason: ${reason}`);1890} else {1891this.logService.trace(`[Extensions]: Checking for updates`);1892}1893if (!this.galleryService.isEnabled()) {1894return;1895}1896const extensions: Extensions[] = [];1897if (this.localExtensions) {1898extensions.push(this.localExtensions);1899}1900if (this.remoteExtensions) {1901extensions.push(this.remoteExtensions);1902}1903if (this.webExtensions) {1904extensions.push(this.webExtensions);1905}1906if (!extensions.length) {1907return;1908}1909const infos: IExtensionInfo[] = [];1910for (const installed of this.local) {1911if (onlyBuiltin && !installed.isBuiltin) {1912// Skip if check updates only for builtin extensions and current extension is not builtin.1913continue;1914}1915if (installed.isBuiltin && !installed.local?.pinned && (installed.type === ExtensionType.System || !installed.local?.identifier.uuid)) {1916// Skip checking updates for a builtin extension if it is a system extension or if it does not has Marketplace identifier1917continue;1918}1919if (installed.local?.source === 'resource') {1920continue;1921}1922infos.push({ ...installed.identifier, preRelease: !!installed.local?.preRelease });1923}1924if (infos.length) {1925const targetPlatform = await extensions[0].server.extensionManagementService.getTargetPlatform();1926type GalleryServiceUpdatesCheckClassification = {1927owner: 'sandy081';1928comment: 'Report when a request is made to check for updates of extensions';1929count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to check update' };1930};1931type GalleryServiceUpdatesCheckEvent = {1932count: number;1933};1934this.telemetryService.publicLog2<GalleryServiceUpdatesCheckEvent, GalleryServiceUpdatesCheckClassification>('galleryService:checkingForUpdates', {1935count: infos.length,1936});1937this.logService.trace(`Checking updates for extensions`, infos.map(e => e.id).join(', '));1938const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion() }, CancellationToken.None);1939if (galleryExtensions.length) {1940await this.syncInstalledExtensionsWithGallery(galleryExtensions, infos);1941}1942}1943}19441945async updateAll(): Promise<InstallExtensionResult[]> {1946const toUpdate: InstallExtensionInfo[] = [];1947this.outdated.forEach((extension) => {1948if (extension.gallery) {1949toUpdate.push({1950extension: extension.gallery,1951options: {1952operation: InstallOperation.Update,1953installPreReleaseVersion: extension.local?.isPreReleaseVersion,1954profileLocation: this.userDataProfileService.currentProfile.extensionsResource,1955isApplicationScoped: extension.local?.isApplicationScoped,1956context: { [EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT]: true }1957}1958});1959}1960});1961return this.extensionManagementService.installGalleryExtensions(toUpdate);1962}19631964async downloadVSIX(extensionId: string, versionKind: 'prerelease' | 'release' | 'any'): Promise<void> {1965let version: IGalleryExtensionVersion | undefined;1966if (versionKind === 'any') {1967version = await this.pickVersionToDownload(extensionId);1968if (!version) {1969return;1970}1971}19721973const extensionInfo = version ? { id: extensionId, version: version.version } : { id: extensionId, preRelease: versionKind === 'prerelease' };1974const queryOptions: IExtensionQueryOptions = version ? {} : { compatible: true };19751976let [galleryExtension] = await this.galleryService.getExtensions([extensionInfo], queryOptions, CancellationToken.None);1977if (!galleryExtension) {1978throw new Error(nls.localize('extension not found', "Extension '{0}' not found.", extensionId));1979}19801981let targetPlatform = galleryExtension.properties.targetPlatform;1982const options = [];1983for (const targetPlatform of version?.targetPlatforms ?? galleryExtension.allTargetPlatforms) {1984if (targetPlatform !== TargetPlatform.UNKNOWN && targetPlatform !== TargetPlatform.UNIVERSAL) {1985options.push({1986label: targetPlatform === TargetPlatform.UNDEFINED ? nls.localize('allplatforms', "All Platforms") : TargetPlatformToString(targetPlatform),1987id: targetPlatform1988});1989}1990}1991if (options.length > 1) {1992const message = nls.localize('platform placeholder', "Please select the platform for which you want to download the VSIX");1993const option = await this.quickInputService.pick(options.sort((a, b) => a.label.localeCompare(b.label)), { placeHolder: message });1994if (!option) {1995return;1996}1997targetPlatform = option.id;1998}19992000if (targetPlatform !== galleryExtension.properties.targetPlatform) {2001[galleryExtension] = await this.galleryService.getExtensions([extensionInfo], { ...queryOptions, targetPlatform }, CancellationToken.None);2002}20032004const result = await this.fileDialogService.showOpenDialog({2005title: nls.localize('download title', "Select folder to download the VSIX"),2006canSelectFiles: false,2007canSelectFolders: true,2008canSelectMany: false,2009openLabel: nls.localize('download', "Download"),2010});20112012if (!result?.[0]) {2013return;2014}20152016this.progressService.withProgress({ location: ProgressLocation.Notification }, async progress => {2017try {2018progress.report({ message: nls.localize('downloading...', "Downloading VSIX...") });2019const name = `${galleryExtension.identifier.id}-${galleryExtension.version}${targetPlatform !== TargetPlatform.UNDEFINED && targetPlatform !== TargetPlatform.UNIVERSAL && targetPlatform !== TargetPlatform.UNKNOWN ? `-${targetPlatform}` : ''}.vsix`;2020await this.galleryService.download(galleryExtension, this.uriIdentityService.extUri.joinPath(result[0], name), InstallOperation.None);2021this.notificationService.info(nls.localize('download.completed', "Successfully downloaded the VSIX"));2022} catch (error) {2023this.notificationService.error(nls.localize('download.failed', "Error while downloading the VSIX: {0}", getErrorMessage(error)));2024}2025});2026}20272028private async pickVersionToDownload(extensionId: string): Promise<IGalleryExtensionVersion | undefined> {2029const allVersions = await this.galleryService.getAllVersions({ id: extensionId });2030if (!allVersions.length) {2031await this.dialogService.info(nls.localize('no versions', "This extension has no other versions."));2032return;2033}20342035const picks = allVersions.map((v, i) => {2036return {2037id: v.version,2038label: v.version,2039description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${nls.localize('pre-release', "pre-release")})` : ''}`,2040ariaLabel: `${v.isPreReleaseVersion ? 'Pre-Release version' : 'Release version'} ${v.version}`,2041data: v,2042};2043});2044const pick = await this.quickInputService.pick(picks,2045{2046placeHolder: nls.localize('selectVersion', "Select Version to Download"),2047matchOnDetail: true2048});2049return pick?.data;2050}20512052private async syncInstalledExtensionsWithGallery(gallery: IGalleryExtension[], flagExtensionsMissingFromGallery?: IExtensionInfo[]): Promise<void> {2053const extensions: Extensions[] = [];2054if (this.localExtensions) {2055extensions.push(this.localExtensions);2056}2057if (this.remoteExtensions) {2058extensions.push(this.remoteExtensions);2059}2060if (this.webExtensions) {2061extensions.push(this.webExtensions);2062}2063if (!extensions.length) {2064return;2065}2066await Promise.allSettled(extensions.map(extensions => extensions.syncInstalledExtensionsWithGallery(gallery, this.getProductVersion(), flagExtensionsMissingFromGallery)));2067if (this.outdated.length) {2068this.logService.info(`Auto updating outdated extensions.`, this.outdated.map(e => e.identifier.id).join(', '));2069this.eventuallyAutoUpdateExtensions();2070}2071}20722073private isAutoCheckUpdatesEnabled(): boolean {2074return this.configurationService.getValue(AutoCheckUpdatesConfigurationKey);2075}20762077private eventuallyCheckForUpdates(immediate = false): void {2078this.updatesCheckDelayer.cancel();2079this.updatesCheckDelayer.trigger(async () => {2080if (this.isAutoCheckUpdatesEnabled()) {2081await this.checkForUpdates();2082}2083this.eventuallyCheckForUpdates();2084}, immediate ? 0 : this.getUpdatesCheckInterval()).then(undefined, err => null);2085}20862087private getUpdatesCheckInterval(): number {2088if (this.productService.quality === 'insider' && this.getProductUpdateVersion()) {2089return 1000 * 60 * 60 * 1; // 1 hour2090}2091return ExtensionsWorkbenchService.UpdatesCheckInterval;2092}20932094private eventuallyAutoUpdateExtensions(): void {2095this.autoUpdateDelayer.trigger(() => this.autoUpdateExtensions())2096.then(undefined, err => null);2097}20982099private async autoUpdateBuiltinExtensions(): Promise<void> {2100await this.checkForUpdates(undefined, true);2101const toUpdate = this.outdated.filter(e => e.isBuiltin);2102await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true } : undefined)));2103}21042105private async syncPinnedBuiltinExtensions(): Promise<void> {2106const infos: IExtensionInfo[] = [];2107for (const installed of this.local) {2108if (installed.isBuiltin && installed.local?.pinned && installed.local?.identifier.uuid) {2109infos.push({ ...installed.identifier, version: installed.version });2110}2111}2112if (infos.length) {2113const galleryExtensions = await this.galleryService.getExtensions(infos, CancellationToken.None);2114if (galleryExtensions.length) {2115await this.syncInstalledExtensionsWithGallery(galleryExtensions);2116}2117}2118}21192120private async autoUpdateExtensions(): Promise<void> {2121const toUpdate: IExtension[] = [];2122const disabledAutoUpdate = [];2123const consentRequired = [];2124for (const extension of this.outdated) {2125if (!this.shouldAutoUpdateExtension(extension)) {2126disabledAutoUpdate.push(extension.identifier.id);2127continue;2128}2129if (await this.shouldRequireConsentToUpdate(extension)) {2130consentRequired.push(extension.identifier.id);2131continue;2132}2133toUpdate.push(extension);2134}21352136if (disabledAutoUpdate.length) {2137this.logService.trace('Auto update disabled for extensions', disabledAutoUpdate.join(', '));2138}21392140if (consentRequired.length) {2141this.logService.info('Auto update consent required for extensions', consentRequired.join(', '));2142}21432144if (!toUpdate.length) {2145return;2146}21472148const productVersion = this.getProductVersion();2149await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true, productVersion } : { productVersion })));2150}21512152private getProductVersion(): IProductVersion {2153return this.getProductUpdateVersion() ?? this.getProductCurrentVersion();2154}21552156private getProductCurrentVersion(): IProductVersion {2157return { version: this.productService.version, date: this.productService.date };2158}21592160private getProductUpdateVersion(): IProductVersion | undefined {2161switch (this.updateService.state.type) {2162case StateType.AvailableForDownload:2163case StateType.Downloaded:2164case StateType.Updating:2165case StateType.Ready: {2166const version = this.updateService.state.update.productVersion;2167if (version && semver.valid(version)) {2168return { version, date: this.updateService.state.update.timestamp ? new Date(this.updateService.state.update.timestamp).toISOString() : undefined };2169}2170}2171}2172return undefined;2173}21742175private shouldAutoUpdateExtension(extension: IExtension): boolean {2176if (extension.deprecationInfo?.disallowInstall) {2177return false;2178}21792180const autoUpdateValue = this.getAutoUpdateValue();21812182if (autoUpdateValue === false) {2183const extensionsToAutoUpdate = this.getEnabledAutoUpdateExtensions();2184const extensionId = extension.identifier.id.toLowerCase();2185if (extensionsToAutoUpdate.includes(extensionId)) {2186return true;2187}2188if (this.isAutoUpdateEnabledForPublisher(extension.publisher) && !extensionsToAutoUpdate.includes(`-${extensionId}`)) {2189return true;2190}2191return false;2192}21932194if (extension.pinned) {2195return false;2196}21972198const disabledAutoUpdateExtensions = this.getDisabledAutoUpdateExtensions();2199if (disabledAutoUpdateExtensions.includes(extension.identifier.id.toLowerCase())) {2200return false;2201}22022203if (autoUpdateValue === true) {2204return true;2205}22062207if (autoUpdateValue === 'onlyEnabledExtensions') {2208return extension.enablementState !== EnablementState.DisabledGlobally && extension.enablementState !== EnablementState.DisabledWorkspace;2209}22102211return false;2212}22132214async shouldRequireConsentToUpdate(extension: IExtension): Promise<string | undefined> {2215if (!extension.outdated) {2216return;2217}22182219if (!extension.gallery || !extension.local) {2220return;2221}22222223if (extension.local.identifier.uuid && extension.local.identifier.uuid !== extension.gallery.identifier.uuid) {2224return nls.localize('consentRequiredToUpdateRepublishedExtension', "The marketplace metadata of this extension changed, likely due to a re-publish.");2225}22262227if (!extension.local.manifest.engines.vscode || extension.local.manifest.main || extension.local.manifest.browser) {2228return;2229}22302231if (isDefined(extension.gallery.properties?.executesCode)) {2232if (!extension.gallery.properties.executesCode) {2233return;2234}2235} else {2236const manifest = extension instanceof Extension2237? await extension.getGalleryManifest()2238: await this.galleryService.getManifest(extension.gallery, CancellationToken.None);2239if (!manifest?.main && !manifest?.browser) {2240return;2241}2242}22432244return nls.localize('consentRequiredToUpdate', "The update for {0} extension introduces executable code, which is not present in the currently installed version.", extension.displayName);2245}22462247isAutoUpdateEnabledFor(extensionOrPublisher: IExtension | string): boolean {2248if (isString(extensionOrPublisher)) {2249if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) {2250throw new Error('Expected publisher string, found extension identifier');2251}2252if (this.isAutoUpdateEnabled()) {2253return true;2254}2255return this.isAutoUpdateEnabledForPublisher(extensionOrPublisher);2256}2257return this.shouldAutoUpdateExtension(extensionOrPublisher);2258}22592260private isAutoUpdateEnabledForPublisher(publisher: string): boolean {2261const publishersToAutoUpdate = this.getPublishersToAutoUpdate();2262return publishersToAutoUpdate.includes(publisher.toLowerCase());2263}22642265async updateAutoUpdateEnablementFor(extensionOrPublisher: IExtension | string, enable: boolean): Promise<void> {2266if (this.isAutoUpdateEnabled()) {2267if (isString(extensionOrPublisher)) {2268throw new Error('Expected extension, found publisher string');2269}2270const disabledAutoUpdateExtensions = this.getDisabledAutoUpdateExtensions();2271const extensionId = extensionOrPublisher.identifier.id.toLowerCase();2272const extensionIndex = disabledAutoUpdateExtensions.indexOf(extensionId);2273if (enable) {2274if (extensionIndex !== -1) {2275disabledAutoUpdateExtensions.splice(extensionIndex, 1);2276}2277}2278else {2279if (extensionIndex === -1) {2280disabledAutoUpdateExtensions.push(extensionId);2281}2282}2283this.setDisabledAutoUpdateExtensions(disabledAutoUpdateExtensions);2284if (enable && extensionOrPublisher.local && extensionOrPublisher.pinned) {2285await this.extensionManagementService.updateMetadata(extensionOrPublisher.local, { pinned: false });2286}2287this._onChange.fire(extensionOrPublisher);2288}22892290else {2291const enabledAutoUpdateExtensions = this.getEnabledAutoUpdateExtensions();2292if (isString(extensionOrPublisher)) {2293if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) {2294throw new Error('Expected publisher string, found extension identifier');2295}2296extensionOrPublisher = extensionOrPublisher.toLowerCase();2297if (this.isAutoUpdateEnabledFor(extensionOrPublisher) !== enable) {2298if (enable) {2299enabledAutoUpdateExtensions.push(extensionOrPublisher);2300} else {2301if (enabledAutoUpdateExtensions.includes(extensionOrPublisher)) {2302enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionOrPublisher), 1);2303}2304}2305}2306this.setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions);2307for (const e of this.installed) {2308if (e.publisher.toLowerCase() === extensionOrPublisher) {2309this._onChange.fire(e);2310}2311}2312} else {2313const extensionId = extensionOrPublisher.identifier.id.toLowerCase();2314const enableAutoUpdatesForPublisher = this.isAutoUpdateEnabledFor(extensionOrPublisher.publisher.toLowerCase());2315const enableAutoUpdatesForExtension = enabledAutoUpdateExtensions.includes(extensionId);2316const disableAutoUpdatesForExtension = enabledAutoUpdateExtensions.includes(`-${extensionId}`);23172318if (enable) {2319if (disableAutoUpdatesForExtension) {2320enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(`-${extensionId}`), 1);2321}2322if (enableAutoUpdatesForPublisher) {2323if (enableAutoUpdatesForExtension) {2324enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionId), 1);2325}2326} else {2327if (!enableAutoUpdatesForExtension) {2328enabledAutoUpdateExtensions.push(extensionId);2329}2330}2331}2332// Disable Auto Updates2333else {2334if (enableAutoUpdatesForExtension) {2335enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionId), 1);2336}2337if (enableAutoUpdatesForPublisher) {2338if (!disableAutoUpdatesForExtension) {2339enabledAutoUpdateExtensions.push(`-${extensionId}`);2340}2341} else {2342if (disableAutoUpdatesForExtension) {2343enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(`-${extensionId}`), 1);2344}2345}2346}2347this.setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions);2348this._onChange.fire(extensionOrPublisher);2349}2350}23512352if (enable) {2353this.autoUpdateExtensions();2354}2355}23562357private onDidSelectedExtensionToAutoUpdateValueChange(): void {2358if (2359this.enabledAuotUpdateExtensionsValue !== this.getEnabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */2360|| this.disabledAutoUpdateExtensionsValue !== this.getDisabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */2361) {2362const userExtensions = this.installed.filter(e => !e.isBuiltin);2363const groupBy = (extensions: IExtension[]): IExtension[][] => {2364const shouldAutoUpdate: IExtension[] = [];2365const shouldNotAutoUpdate: IExtension[] = [];2366for (const extension of extensions) {2367if (this.shouldAutoUpdateExtension(extension)) {2368shouldAutoUpdate.push(extension);2369} else {2370shouldNotAutoUpdate.push(extension);2371}2372}2373return [shouldAutoUpdate, shouldNotAutoUpdate];2374};23752376const [wasShouldAutoUpdate, wasShouldNotAutoUpdate] = groupBy(userExtensions);2377this._enabledAutoUpdateExtensionsValue = undefined;2378this._disabledAutoUpdateExtensionsValue = undefined;2379const [shouldAutoUpdate, shouldNotAutoUpdate] = groupBy(userExtensions);23802381for (const e of wasShouldAutoUpdate ?? []) {2382if (shouldNotAutoUpdate?.includes(e)) {2383this._onChange.fire(e);2384}2385}2386for (const e of wasShouldNotAutoUpdate ?? []) {2387if (shouldAutoUpdate?.includes(e)) {2388this._onChange.fire(e);2389}2390}2391}2392}23932394async canInstall(extension: IExtension): Promise<true | IMarkdownString> {2395if (!(extension instanceof Extension)) {2396return new MarkdownString().appendText(nls.localize('not an extension', "The provided object is not an extension."));2397}23982399if (extension.isMalicious) {2400return new MarkdownString().appendText(nls.localize('malicious', "This extension is reported to be problematic."));2401}24022403if (extension.deprecationInfo?.disallowInstall) {2404return new MarkdownString().appendText(nls.localize('disallowed', "This extension is disallowed to be installed."));2405}24062407if (extension.gallery) {2408if (!extension.gallery.isSigned && shouldRequireRepositorySignatureFor(extension.private, await this.extensionGalleryManifestService.getExtensionGalleryManifest())) {2409return new MarkdownString().appendText(nls.localize('not signed', "This extension is not signed."));2410}24112412const localResult = this.localExtensions ? await this.localExtensions.canInstall(extension.gallery) : undefined;2413if (localResult === true) {2414return true;2415}24162417const remoteResult = this.remoteExtensions ? await this.remoteExtensions.canInstall(extension.gallery) : undefined;2418if (remoteResult === true) {2419return true;2420}24212422const webResult = this.webExtensions ? await this.webExtensions.canInstall(extension.gallery) : undefined;2423if (webResult === true) {2424return true;2425}24262427return 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));2428}24292430if (extension.resourceExtension && await this.extensionManagementService.canInstall(extension.resourceExtension) === true) {2431return true;2432}24332434return 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));2435}24362437async install(arg: string | URI | IExtension, installOptions: InstallExtensionOptions = {}, progressLocation?: ProgressLocation | string): Promise<IExtension> {2438let installable: URI | IGalleryExtension | IResourceExtension | undefined;2439let extension: IExtension | undefined;2440let servers: IExtensionManagementServer[] | undefined;24412442if (arg instanceof URI) {2443installable = arg;2444} else {2445let installableInfo: IExtensionInfo | undefined;2446let gallery: IGalleryExtension | undefined;24472448// Install by id2449if (isString(arg)) {2450extension = this.local.find(e => areSameExtensions(e.identifier, { id: arg }));2451if (!extension?.isBuiltin) {2452installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions.installPreReleaseVersion ?? this.extensionManagementService.preferPreReleases };2453}2454}2455// Install by gallery2456else if (arg.gallery) {2457extension = arg;2458gallery = arg.gallery;2459if (installOptions.version && installOptions.version !== gallery?.version) {2460installableInfo = { id: extension.identifier.id, version: installOptions.version };2461}2462}2463// Install by resource2464else if (arg.resourceExtension) {2465extension = arg;2466installable = arg.resourceExtension;2467}24682469if (installableInfo) {2470const targetPlatform = extension?.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined;2471gallery = (await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None)).at(0);2472}24732474if (!extension && gallery) {2475extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined);2476(<Extension>extension).setExtensionsControlManifest(await this.extensionManagementService.getExtensionsControlManifest());2477}24782479if (extension?.isMalicious) {2480throw new Error(nls.localize('malicious', "This extension is reported to be problematic."));2481}24822483if (gallery) {2484// If requested to install everywhere2485// then install the extension in all the servers where it is not installed2486if (installOptions.installEverywhere) {2487servers = [];2488const installableServers = await this.extensionManagementService.getInstallableServers(gallery);2489for (const extensionsServer of this.extensionsServers) {2490if (installableServers.includes(extensionsServer.server) && !extensionsServer.local.find(e => areSameExtensions(e.identifier, gallery.identifier))) {2491servers.push(extensionsServer.server);2492}2493}2494}2495// If requested to enable and extension is already installed2496// Check if the extension is disabled because of extension kind2497// If so, install the extension in the server that is compatible.2498else if (installOptions.enable && extension?.local) {2499servers = [];2500if (extension.enablementState === EnablementState.DisabledByExtensionKind) {2501const [installableServer] = await this.extensionManagementService.getInstallableServers(gallery);2502if (installableServer) {2503servers.push(installableServer);2504}2505}2506}2507}25082509if (!servers || servers.length) {2510if (!installable) {2511if (!gallery) {2512const id = isString(arg) ? arg : (<IExtension>arg).identifier.id;2513const manifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();2514const reportIssueUri = manifest ? getExtensionGalleryManifestResourceUri(manifest, ExtensionGalleryResourceType.ContactSupportUri) : undefined;2515const reportIssueMessage = reportIssueUri ? nls.localize('report issue', "If this issue persists, please report it at {0}", reportIssueUri.toString()) : '';2516if (installOptions.version) {2517const message = nls.localize('not found version', "The extension '{0}' cannot be installed because the requested version '{1}' was not found.", id, installOptions.version);2518throw new ExtensionManagementError(reportIssueMessage ? `${message} ${reportIssueMessage}` : message, ExtensionManagementErrorCode.NotFound);2519} else {2520const message = nls.localize('not found', "The extension '{0}' cannot be installed because it was not found.", id);2521throw new ExtensionManagementError(reportIssueMessage ? `${message} ${reportIssueMessage}` : message, ExtensionManagementErrorCode.NotFound);2522}2523}2524installable = gallery;2525}2526if (installOptions.version) {2527installOptions.installGivenVersion = true;2528}2529if (extension?.isWorkspaceScoped) {2530installOptions.isWorkspaceScoped = true;2531}2532}2533}25342535if (installable) {2536if (installOptions.justification) {2537const syncCheck = isUndefined(installOptions.isMachineScoped) && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions);2538const buttons: IPromptButton<boolean>[] = [];2539buttons.push({2540label: isString(installOptions.justification) || !installOptions.justification.action2541? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension")2542: nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), run: () => true2543});2544if (!extension) {2545buttons.push({ label: nls.localize('open', "Open Extension"), run: () => { this.open(extension!); return false; } });2546}2547const result = await this.dialogService.prompt<boolean>({2548title: nls.localize('installExtensionTitle', "Install Extension"),2549message: 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?"),2550detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason,2551cancelButton: true,2552buttons,2553checkbox: syncCheck ? {2554label: nls.localize('sync extension', "Sync this extension"),2555checked: true,2556} : undefined,2557});2558if (!result.result) {2559throw new CancellationError();2560}2561if (syncCheck) {2562installOptions.isMachineScoped = !result.checkboxChecked;2563}2564}2565if (installable instanceof URI) {2566extension = await this.doInstall(undefined, () => this.installFromVSIX(installable, installOptions), progressLocation);2567} else if (extension) {2568if (extension.resourceExtension) {2569extension = await this.doInstall(extension, () => this.extensionManagementService.installResourceExtension(installable as IResourceExtension, installOptions), progressLocation);2570} else {2571extension = await this.doInstall(extension, () => this.installFromGallery(extension!, installable as IGalleryExtension, installOptions, servers), progressLocation);2572}2573}2574}25752576if (!extension) {2577throw new Error(nls.localize('unknown', "Unable to install extension"));2578}25792580if (installOptions.enable) {2581if (extension.enablementState === EnablementState.DisabledWorkspace || extension.enablementState === EnablementState.DisabledGlobally) {2582if (installOptions.justification) {2583const result = await this.dialogService.confirm({2584title: nls.localize('enableExtensionTitle', "Enable Extension"),2585message: nls.localize('enableExtensionMessage', "Would you like to enable '{0}' extension?", extension.displayName),2586detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason,2587primaryButton: 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),2588});2589if (!result.confirmed) {2590throw new CancellationError();2591}2592}2593await this.setEnablement(extension, extension.enablementState === EnablementState.DisabledWorkspace ? EnablementState.EnabledWorkspace : EnablementState.EnabledGlobally);2594}2595await this.waitUntilExtensionIsEnabled(extension);2596}25972598return extension;2599}26002601async installInServer(extension: IExtension, server: IExtensionManagementServer, installOptions?: InstallOptions): Promise<void> {2602await this.doInstall(extension, async () => {2603const local = extension.local;2604if (!local) {2605throw new Error('Extension not found');2606}2607if (!extension.gallery) {2608extension = (await this.getExtensions([{ ...extension.identifier, preRelease: local.preRelease }], CancellationToken.None))[0] ?? extension;2609}2610if (extension.gallery) {2611return server.extensionManagementService.installFromGallery(extension.gallery, { installPreReleaseVersion: local.preRelease, ...installOptions });2612}26132614const targetPlatform = await server.extensionManagementService.getTargetPlatform();2615if (!isTargetPlatformCompatible(local.targetPlatform, [local.targetPlatform], targetPlatform)) {2616throw new Error(nls.localize('incompatible', "Can't install '{0}' extension because it is not compatible.", extension.identifier.id));2617}26182619const vsix = await this.extensionManagementService.zip(local);2620try {2621return await server.extensionManagementService.install(vsix);2622} finally {2623try {2624await this.fileService.del(vsix);2625} catch (error) {2626this.logService.error(error);2627}2628}2629});2630}26312632canSetLanguage(extension: IExtension): boolean {2633if (!isWeb) {2634return false;2635}26362637if (!extension.gallery) {2638return false;2639}26402641const locale = getLocale(extension.gallery);2642if (!locale) {2643return false;2644}26452646return true;2647}26482649async setLanguage(extension: IExtension): Promise<void> {2650if (!this.canSetLanguage(extension)) {2651throw new Error('Can not set language');2652}2653const locale = getLocale(extension.gallery!);2654if (locale === language) {2655return;2656}2657const localizedLanguageName = extension.gallery?.properties?.localizedLanguages?.[0];2658return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: localizedLanguageName ?? extension.displayName });2659}26602661setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void> {2662extensions = Array.isArray(extensions) ? extensions : [extensions];2663return this.promptAndSetEnablement(extensions, enablementState);2664}26652666async uninstall(e: IExtension): Promise<void> {2667const extension = e.local ? e : this.local.find(local => areSameExtensions(local.identifier, e.identifier));2668if (!extension?.local) {2669throw new Error('Missing local');2670}26712672if (extension.local.isApplicationScoped && this.userDataProfilesService.profiles.length > 1) {2673const { confirmed } = await this.dialogService.confirm({2674title: nls.localize('uninstallApplicationScoped', "Uninstall Extension"),2675type: Severity.Info,2676message: nls.localize('uninstallApplicationScopedMessage', "Would you like to Uninstall {0} from all profiles?", extension.displayName),2677primaryButton: nls.localize('uninstallAllProfiles', "Uninstall (All Profiles)")2678});2679if (!confirmed) {2680throw new CancellationError();2681}2682}26832684const extensionsToUninstall: UninstallExtensionInfo[] = [{ extension: extension.local }];2685for (const packExtension of this.getAllPackedExtensions(extension, this.local)) {2686if (packExtension.local && !extensionsToUninstall.some(e => areSameExtensions(e.extension.identifier, packExtension.identifier))) {2687extensionsToUninstall.push({ extension: packExtension.local });2688}2689}26902691const dependents: ILocalExtension[] = [];2692let extensionsFromAllProfiles: [ILocalExtension, URI][] | undefined;2693for (const { extension } of extensionsToUninstall) {2694const installedExtensions: [ILocalExtension, URI | undefined][] = [];2695if (extension.isApplicationScoped && this.userDataProfilesService.profiles.length > 1) {2696if (!extensionsFromAllProfiles) {2697extensionsFromAllProfiles = [];2698await Promise.allSettled(this.userDataProfilesService.profiles.map(async profile => {2699const installed = await this.extensionManagementService.getInstalled(ExtensionType.User, profile.extensionsResource);2700for (const local of installed) {2701extensionsFromAllProfiles?.push([local, profile.extensionsResource]);2702}2703}));2704}2705installedExtensions.push(...extensionsFromAllProfiles);2706} else {2707for (const { local } of this.local) {2708if (local) {2709installedExtensions.push([local, undefined]);2710}2711}2712}2713for (const [local, profileLocation] of installedExtensions) {2714if (areSameExtensions(local.identifier, extension.identifier)) {2715continue;2716}2717if (!local.manifest.extensionDependencies || local.manifest.extensionDependencies.length === 0) {2718continue;2719}2720if (extension.manifest.extensionPack?.some(id => areSameExtensions({ id }, local.identifier))) {2721continue;2722}2723if (dependents.some(d => d.manifest.extensionPack?.some(id => areSameExtensions({ id }, local.identifier)))) {2724continue;2725}2726if (local.manifest.extensionDependencies.some(dep => areSameExtensions(extension.identifier, { id: dep }))) {2727dependents.push(local);2728extensionsToUninstall.push({ extension: local, options: { profileLocation } });2729}2730}2731}27322733if (dependents.length) {2734const { result } = await this.dialogService.prompt({2735title: nls.localize('uninstallDependents', "Uninstall Extension with Dependents"),2736type: Severity.Warning,2737message: this.getErrorMessageForUninstallingAnExtensionWithDependents(extension, dependents),2738buttons: [{2739label: nls.localize('uninstallAll', "Uninstall All"),2740run: () => true2741}],2742cancelButton: {2743run: () => false2744}2745});2746if (!result) {2747throw new CancellationError();2748}2749}27502751return this.withProgress({2752location: ProgressLocation.Extensions,2753title: nls.localize('uninstallingExtension', 'Uninstalling extension...'),2754source: `${extension.identifier.id}`2755}, () => this.extensionManagementService.uninstallExtensions(extensionsToUninstall).then(() => undefined));2756}27572758private getAllPackedExtensions(extension: IExtension, installed: IExtension[], checked: IExtension[] = []): IExtension[] {2759if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) {2760return [];2761}2762checked.push(extension);2763const extensionsPack = extension.extensionPack ?? [];2764if (extensionsPack.length) {2765const packedExtensions: IExtension[] = [];2766for (const i of installed) {2767if (!i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier))) {2768packedExtensions.push(i);2769}2770}2771const packOfPackedExtensions: IExtension[] = [];2772for (const packedExtension of packedExtensions) {2773packOfPackedExtensions.push(...this.getAllPackedExtensions(packedExtension, installed, checked));2774}2775return [...packedExtensions, ...packOfPackedExtensions];2776}2777return [];2778}27792780private getErrorMessageForUninstallingAnExtensionWithDependents(extension: IExtension, dependents: ILocalExtension[]): string {2781if (dependents.length === 1) {2782return 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);2783}2784if (dependents.length === 2) {2785return nls.localize('twoDependentsUninstallError', "Cannot uninstall '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to uninstall all these extensions?",2786extension.displayName, dependents[0].manifest.displayName, dependents[1].manifest.displayName);2787}2788return nls.localize('multipleDependentsUninstallError', "Cannot uninstall '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to uninstall all these extensions?",2789extension.displayName, dependents[0].manifest.displayName, dependents[1].manifest.displayName);2790}27912792isExtensionIgnoredToSync(extension: IExtension): boolean {2793return extension.local ? !this.isInstalledExtensionSynced(extension.local)2794: this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id);2795}27962797async togglePreRelease(extension: IExtension): Promise<void> {2798if (!extension.local) {2799return;2800}2801if (extension.preRelease !== extension.isPreReleaseVersion) {2802await this.extensionManagementService.updateMetadata(extension.local, { preRelease: !extension.preRelease });2803return;2804}2805await this.install(extension, { installPreReleaseVersion: !extension.preRelease, preRelease: !extension.preRelease });2806}28072808async toggleExtensionIgnoredToSync(extension: IExtension): Promise<void> {2809const extensionsIncludingPackedExtensions = [extension, ...this.getAllPackedExtensions(extension, this.local)];2810// Updated in sync to prevent race conditions2811for (const e of extensionsIncludingPackedExtensions) {2812const isIgnored = this.isExtensionIgnoredToSync(e);2813if (e.local && isIgnored && e.local.isMachineScoped) {2814await this.extensionManagementService.updateMetadata(e.local, { isMachineScoped: false });2815} else {2816await this.extensionsSyncManagementService.updateIgnoredExtensions(e.identifier.id, !isIgnored);2817}2818}2819await this.userDataAutoSyncService.triggerSync(['IgnoredExtensionsUpdated']);2820}28212822async toggleApplyExtensionToAllProfiles(extension: IExtension): Promise<void> {2823const extensionsIncludingPackedExtensions = [extension, ...this.getAllPackedExtensions(extension, this.local)];2824const allExtensionServers = this.getAllExtensionServers();2825await Promise.allSettled(extensionsIncludingPackedExtensions.map(async e => {2826if (!e.local || isApplicationScopedExtension(e.local.manifest) || e.isBuiltin) {2827return;2828}2829const isApplicationScoped = e.local.isApplicationScoped;2830await Promise.all(allExtensionServers.map(async extensionServer => {2831const local = extensionServer.local.find(local => areSameExtensions(e.identifier, local.identifier))?.local;2832if (local && local.isApplicationScoped === isApplicationScoped) {2833await this.extensionManagementService.toggleApplicationScope(local, this.userDataProfileService.currentProfile.extensionsResource);2834}2835}));2836}));2837}28382839private getAllExtensionServers(): Extensions[] {2840const extensions: Extensions[] = [];2841if (this.localExtensions) {2842extensions.push(this.localExtensions);2843}2844if (this.remoteExtensions) {2845extensions.push(this.remoteExtensions);2846}2847if (this.webExtensions) {2848extensions.push(this.webExtensions);2849}2850return extensions;2851}28522853private isInstalledExtensionSynced(extension: ILocalExtension): boolean {2854if (extension.isMachineScoped) {2855return false;2856}2857if (this.extensionsSyncManagementService.hasToAlwaysSyncExtension(extension.identifier.id)) {2858return true;2859}2860return !this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id);2861}28622863private doInstall(extension: IExtension | undefined, installTask: () => Promise<ILocalExtension>, progressLocation?: ProgressLocation | string): Promise<IExtension> {2864const title = extension ? nls.localize('installing named extension', "Installing '{0}' extension...", extension.displayName) : nls.localize('installing extension', 'Installing extension...');2865return this.withProgress({2866location: progressLocation ?? ProgressLocation.Extensions,2867title2868}, async () => {2869try {2870if (extension) {2871this.installing.push(extension);2872this._onChange.fire(extension);2873}2874const local = await installTask();2875return await this.waitAndGetInstalledExtension(local.identifier);2876} finally {2877if (extension) {2878this.installing = this.installing.filter(e => e !== extension);2879// Trigger the change without passing the extension because it is replaced by a new instance.2880this._onChange.fire(undefined);2881}2882}2883});2884}28852886private async installFromVSIX(vsix: URI, installOptions: InstallOptions): Promise<ILocalExtension> {2887const manifest = await this.extensionManagementService.getManifest(vsix);2888const existingExtension = this.local.find(local => areSameExtensions(local.identifier, { id: getGalleryExtensionId(manifest.publisher, manifest.name) }));2889if (existingExtension) {2890installOptions = installOptions || {};2891if (existingExtension.latestVersion === manifest.version) {2892installOptions.pinned = installOptions.pinned ?? (existingExtension.local?.pinned || !this.shouldAutoUpdateExtension(existingExtension));2893} else {2894installOptions.installGivenVersion = true;2895}2896}2897return this.extensionManagementService.installVSIX(vsix, manifest, installOptions);2898}28992900private installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions: InstallExtensionOptions, servers: IExtensionManagementServer[] | undefined): Promise<ILocalExtension> {2901installOptions = installOptions ?? {};2902installOptions.pinned = installOptions.pinned ?? (extension.local?.pinned || !this.shouldAutoUpdateExtension(extension));2903if (extension.local && !servers) {2904installOptions.productVersion = this.getProductVersion();2905installOptions.operation = InstallOperation.Update;2906return this.extensionManagementService.updateFromGallery(gallery, extension.local, installOptions);2907} else {2908return this.extensionManagementService.installFromGallery(gallery, installOptions, servers);2909}2910}29112912private async waitAndGetInstalledExtension(identifier: IExtensionIdentifier): Promise<IExtension> {2913let installedExtension = this.local.find(local => areSameExtensions(local.identifier, identifier));2914if (!installedExtension) {2915await Event.toPromise(Event.filter(this.onChange, e => !!e && this.local.some(local => areSameExtensions(local.identifier, identifier))));2916}2917installedExtension = this.local.find(local => areSameExtensions(local.identifier, identifier));2918if (!installedExtension) {2919// This should not happen2920throw new Error('Extension should have been installed');2921}2922return installedExtension;2923}29242925private async waitUntilExtensionIsEnabled(extension: IExtension): Promise<void> {2926if (this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension.identifier.id))) {2927return;2928}2929if (!extension.local || !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) {2930return;2931}2932await new Promise<void>((c, e) => {2933const disposable = this.extensionService.onDidChangeExtensions(() => {2934try {2935if (this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension.identifier.id))) {2936disposable.dispose();2937c();2938}2939} catch (error) {2940e(error);2941}2942});2943});2944}29452946private promptAndSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise<any> {2947const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;2948if (enable) {2949const allDependenciesAndPackedExtensions = this.getExtensionsRecursively(extensions, this.local, enablementState, { dependencies: true, pack: true });2950return this.checkAndSetEnablement(extensions, allDependenciesAndPackedExtensions, enablementState);2951} else {2952const packedExtensions = this.getExtensionsRecursively(extensions, this.local, enablementState, { dependencies: false, pack: true });2953if (packedExtensions.length) {2954return this.checkAndSetEnablement(extensions, packedExtensions, enablementState);2955}2956return this.checkAndSetEnablement(extensions, [], enablementState);2957}2958}29592960private async checkAndSetEnablement(extensions: IExtension[], otherExtensions: IExtension[], enablementState: EnablementState): Promise<any> {2961const allExtensions = [...extensions, ...otherExtensions];2962const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;2963if (!enable) {2964for (const extension of extensions) {2965const dependents = this.getDependentsAfterDisablement(extension, allExtensions, this.local);2966if (dependents.length) {2967const { result } = await this.dialogService.prompt({2968title: nls.localize('disableDependents', "Disable Extension with Dependents"),2969type: Severity.Warning,2970message: this.getDependentsErrorMessageForDisablement(extension, allExtensions, dependents),2971buttons: [{2972label: nls.localize('disable all', 'Disable All'),2973run: () => true2974}],2975cancelButton: {2976run: () => false2977}2978});2979if (!result) {2980throw new CancellationError();2981}2982await this.checkAndSetEnablement(dependents, [extension], enablementState);2983}2984}2985}2986return this.doSetEnablement(allExtensions, enablementState);2987}29882989private getExtensionsRecursively(extensions: IExtension[], installed: IExtension[], enablementState: EnablementState, options: { dependencies: boolean; pack: boolean }, checked: IExtension[] = []): IExtension[] {2990const toCheck = extensions.filter(e => checked.indexOf(e) === -1);2991if (toCheck.length) {2992for (const extension of toCheck) {2993checked.push(extension);2994}2995const extensionsToEanbleOrDisable = installed.filter(i => {2996if (checked.indexOf(i) !== -1) {2997return false;2998}2999const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;3000const isExtensionEnabled = i.enablementState === EnablementState.EnabledGlobally || i.enablementState === EnablementState.EnabledWorkspace;3001if (enable === isExtensionEnabled) {3002return false;3003}3004return (enable || !i.isBuiltin) // Include all Extensions for enablement and only non builtin extensions for disablement3005&& (options.dependencies || options.pack)3006&& extensions.some(extension =>3007(options.dependencies && extension.dependencies.some(id => areSameExtensions({ id }, i.identifier)))3008|| (options.pack && extension.extensionPack.some(id => areSameExtensions({ id }, i.identifier)))3009);3010});3011if (extensionsToEanbleOrDisable.length) {3012extensionsToEanbleOrDisable.push(...this.getExtensionsRecursively(extensionsToEanbleOrDisable, installed, enablementState, options, checked));3013}3014return extensionsToEanbleOrDisable;3015}3016return [];3017}30183019private getDependentsAfterDisablement(extension: IExtension, extensionsToDisable: IExtension[], installed: IExtension[]): IExtension[] {3020return installed.filter(i => {3021if (i.dependencies.length === 0) {3022return false;3023}3024if (i === extension) {3025return false;3026}3027if (!this.extensionEnablementService.isEnabledEnablementState(i.enablementState)) {3028return false;3029}3030if (extensionsToDisable.indexOf(i) !== -1) {3031return false;3032}3033return i.dependencies.some(dep => [extension, ...extensionsToDisable].some(d => areSameExtensions(d.identifier, { id: dep })));3034});3035}30363037private getDependentsErrorMessageForDisablement(extension: IExtension, allDisabledExtensions: IExtension[], dependents: IExtension[]): string {3038for (const e of [extension, ...allDisabledExtensions]) {3039const dependentsOfTheExtension = dependents.filter(d => d.dependencies.some(id => areSameExtensions({ id }, e.identifier)));3040if (dependentsOfTheExtension.length) {3041return this.getErrorMessageForDisablingAnExtensionWithDependents(e, dependentsOfTheExtension);3042}3043}3044return '';3045}30463047private getErrorMessageForDisablingAnExtensionWithDependents(extension: IExtension, dependents: IExtension[]): string {3048if (dependents.length === 1) {3049return 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);3050}3051if (dependents.length === 2) {3052return nls.localize('twoDependentsError', "Cannot disable '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to disable all these extensions?",3053extension.displayName, dependents[0].displayName, dependents[1].displayName);3054}3055return nls.localize('multipleDependentsError', "Cannot disable '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to disable all these extensions?",3056extension.displayName, dependents[0].displayName, dependents[1].displayName);3057}30583059private async doSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise<boolean[]> {3060return await this.extensionEnablementService.setEnablement(extensions.map(e => e.local!), enablementState);3061}30623063// Current service reports progress when installing/uninstalling extensions3064// This is to report progress for other sources of extension install/uninstall changes3065// Since we cannot differentiate between the two, we report progress for all extension install/uninstall changes3066private _activityCallBack: ((value: void) => void) | undefined;3067private reportProgressFromOtherSources(): void {3068if (this.installed.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling)) {3069if (!this._activityCallBack) {3070this.withProgress({ location: ProgressLocation.Extensions }, () => new Promise(resolve => this._activityCallBack = resolve));3071}3072} else {3073this._activityCallBack?.();3074this._activityCallBack = undefined;3075}3076}30773078private withProgress<T>(options: IProgressOptions, task: () => Promise<T>): Promise<T> {3079return this.progressService.withProgress(options, async () => {3080const cancelableTask = createCancelablePromise(() => task());3081this.tasksInProgress.push(cancelableTask);3082try {3083return await cancelableTask;3084} finally {3085const index = this.tasksInProgress.indexOf(cancelableTask);3086if (index !== -1) {3087this.tasksInProgress.splice(index, 1);3088}3089}3090});3091}30923093private onError(err: any): void {3094if (isCancellationError(err)) {3095return;3096}30973098const message = err && err.message || '';30993100if (/getaddrinfo ENOTFOUND|getaddrinfo ENOENT|connect EACCES|connect ECONNREFUSED/.test(message)) {3101return;3102}31033104this.notificationService.error(err);3105}31063107handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {3108if (!/^extension/.test(uri.path)) {3109return Promise.resolve(false);3110}31113112this.onOpenExtensionUrl(uri);3113return Promise.resolve(true);3114}31153116private onOpenExtensionUrl(uri: URI): void {3117const match = /^extension\/([^/]+)$/.exec(uri.path);31183119if (!match) {3120return;3121}31223123const extensionId = match[1];31243125this.queryLocal().then(async local => {3126let extension = local.find(local => areSameExtensions(local.identifier, { id: extensionId }));3127if (!extension) {3128[extension] = await this.getExtensions([{ id: extensionId }], { source: 'uri' }, CancellationToken.None);3129}3130if (extension) {3131await this.hostService.focus(mainWindow);3132await this.open(extension);3133}3134}).then(undefined, error => this.onError(error));3135}31363137private getPublishersToAutoUpdate(): string[] {3138return this.getEnabledAutoUpdateExtensions().filter(id => !EXTENSION_IDENTIFIER_REGEX.test(id));3139}31403141getEnabledAutoUpdateExtensions(): string[] {3142try {3143const parsedValue = JSON.parse(this.enabledAuotUpdateExtensionsValue);3144if (Array.isArray(parsedValue)) {3145return parsedValue;3146}3147} catch (e) { /* Ignore */ }3148return [];3149}31503151private setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions: string[]): void {3152this.enabledAuotUpdateExtensionsValue = JSON.stringify(enabledAutoUpdateExtensions);3153}31543155private _enabledAutoUpdateExtensionsValue: string | undefined;3156private get enabledAuotUpdateExtensionsValue(): string {3157if (!this._enabledAutoUpdateExtensionsValue) {3158this._enabledAutoUpdateExtensionsValue = this.getEnabledAutoUpdateExtensionsValue();3159}31603161return this._enabledAutoUpdateExtensionsValue;3162}31633164private set enabledAuotUpdateExtensionsValue(enabledAuotUpdateExtensionsValue: string) {3165if (this.enabledAuotUpdateExtensionsValue !== enabledAuotUpdateExtensionsValue) {3166this._enabledAutoUpdateExtensionsValue = enabledAuotUpdateExtensionsValue;3167this.setEnabledAutoUpdateExtensionsValue(enabledAuotUpdateExtensionsValue);3168}3169}31703171private getEnabledAutoUpdateExtensionsValue(): string {3172return this.storageService.get(EXTENSIONS_AUTO_UPDATE_KEY, StorageScope.APPLICATION, '[]');3173}31743175private setEnabledAutoUpdateExtensionsValue(value: string): void {3176this.storageService.store(EXTENSIONS_AUTO_UPDATE_KEY, value, StorageScope.APPLICATION, StorageTarget.USER);3177}31783179getDisabledAutoUpdateExtensions(): string[] {3180try {3181const parsedValue = JSON.parse(this.disabledAutoUpdateExtensionsValue);3182if (Array.isArray(parsedValue)) {3183return parsedValue;3184}3185} catch (e) { /* Ignore */ }3186return [];3187}31883189private setDisabledAutoUpdateExtensions(disabledAutoUpdateExtensions: string[]): void {3190this.disabledAutoUpdateExtensionsValue = JSON.stringify(disabledAutoUpdateExtensions);3191}31923193private _disabledAutoUpdateExtensionsValue: string | undefined;3194private get disabledAutoUpdateExtensionsValue(): string {3195if (!this._disabledAutoUpdateExtensionsValue) {3196this._disabledAutoUpdateExtensionsValue = this.getDisabledAutoUpdateExtensionsValue();3197}31983199return this._disabledAutoUpdateExtensionsValue;3200}32013202private set disabledAutoUpdateExtensionsValue(disabledAutoUpdateExtensionsValue: string) {3203if (this.disabledAutoUpdateExtensionsValue !== disabledAutoUpdateExtensionsValue) {3204this._disabledAutoUpdateExtensionsValue = disabledAutoUpdateExtensionsValue;3205this.setDisabledAutoUpdateExtensionsValue(disabledAutoUpdateExtensionsValue);3206}3207}32083209private getDisabledAutoUpdateExtensionsValue(): string {3210return this.storageService.get(EXTENSIONS_DONOT_AUTO_UPDATE_KEY, StorageScope.APPLICATION, '[]');3211}32123213private setDisabledAutoUpdateExtensionsValue(value: string): void {3214this.storageService.store(EXTENSIONS_DONOT_AUTO_UPDATE_KEY, value, StorageScope.APPLICATION, StorageTarget.USER);3215}32163217private getDismissedNotifications(): string[] {3218try {3219const parsedValue = JSON.parse(this.dismissedNotificationsValue);3220if (Array.isArray(parsedValue)) {3221return parsedValue;3222}3223} catch (e) { /* Ignore */ }3224return [];3225}32263227private setDismissedNotifications(dismissedNotifications: string[]): void {3228this.dismissedNotificationsValue = JSON.stringify(dismissedNotifications);3229}32303231private _dismissedNotificationsValue: string | undefined;3232private get dismissedNotificationsValue(): string {3233if (!this._dismissedNotificationsValue) {3234this._dismissedNotificationsValue = this.getDismissedNotificationsValue();3235}32363237return this._dismissedNotificationsValue;3238}32393240private set dismissedNotificationsValue(dismissedNotificationsValue: string) {3241if (this.dismissedNotificationsValue !== dismissedNotificationsValue) {3242this._dismissedNotificationsValue = dismissedNotificationsValue;3243this.setDismissedNotificationsValue(dismissedNotificationsValue);3244}3245}32463247private getDismissedNotificationsValue(): string {3248return this.storageService.get(EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, StorageScope.PROFILE, '[]');3249}32503251private setDismissedNotificationsValue(value: string): void {3252this.storageService.store(EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, value, StorageScope.PROFILE, StorageTarget.USER);3253}32543255}325632573258