Path: blob/main/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.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 './media/extensionsViewlet.css';6import { localize, localize2 } from '../../../../nls.js';7import { timeout, Delayer } from '../../../../base/common/async.js';8import { isCancellationError } from '../../../../base/common/errors.js';9import { createErrorWithActions } from '../../../../base/common/errorMessage.js';10import { IWorkbenchContribution } from '../../../common/contributions.js';11import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';12import { Event } from '../../../../base/common/event.js';13import { Action } from '../../../../base/common/actions.js';14import { append, $, Dimension, hide, show, DragAndDropObserver, trackFocus, addDisposableListener, EventType, clearNode } from '../../../../base/browser/dom.js';15import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';16import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';17import { IExtensionService } from '../../../services/extensions/common/extensions.js';18import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, AutoCheckUpdatesConfigurationKey, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, extensionsSearchActionsMenu, AutoRestartConfigurationKey, ExtensionRuntimeActionType, SearchMcpServersContext, DefaultViewsContext, CONTEXT_EXTENSIONS_GALLERY_STATUS } from '../common/extensions.js';19import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAction } from './extensionsActions.js';20import { IExtensionManagementService, ILocalExtension } from '../../../../platform/extensionManagement/common/extensionManagement.js';21import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from '../../../services/extensionManagement/common/extensionManagement.js';22import { ExtensionsInput } from '../common/extensionsInput.js';23import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView, VirtualWorkspacePartiallySupportedExtensionsView, DefaultPopularExtensionsView, DeprecatedExtensionsView, SearchMarketplaceExtensionsView, RecentlyUpdatedExtensionsView, OutdatedExtensionsView, StaticQueryExtensionsView, NONE_CATEGORY, AbstractExtensionsListView } from './extensionsViews.js';24import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';25import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';26import Severity from '../../../../base/common/severity.js';27import { IActivityService, IBadge, NumberBadge, WarningBadge } from '../../../services/activity/common/activity.js';28import { IThemeService } from '../../../../platform/theme/common/themeService.js';29import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';30import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef, ViewContainerLocation } from '../../../common/views.js';31import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';32import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';33import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from '../../../../platform/contextkey/common/contextkey.js';34import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';35import { ILogService } from '../../../../platform/log/common/log.js';36import { INotificationService, IPromptChoice, NotificationPriority } from '../../../../platform/notification/common/notification.js';37import { IHostService } from '../../../services/host/browser/host.js';38import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';39import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';40import { ViewPane } from '../../../browser/parts/views/viewPane.js';41import { Query } from '../common/extensionQuery.js';42import { SuggestEnabledInput } from '../../codeEditor/browser/suggestEnabledInput/suggestEnabledInput.js';43import { alert } from '../../../../base/browser/ui/aria/aria.js';44import { EXTENSION_CATEGORIES } from '../../../../platform/extensions/common/extensions.js';45import { Registry } from '../../../../platform/registry/common/platform.js';46import { ILabelService } from '../../../../platform/label/common/label.js';47import { MementoObject } from '../../../common/memento.js';48import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';49import { IPreferencesService } from '../../../services/preferences/common/preferences.js';50import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js';51import { VirtualWorkspaceContext, WorkbenchStateContext } from '../../../common/contextkeys.js';52import { ICommandService } from '../../../../platform/commands/common/commands.js';53import { installLocalInRemoteIcon } from './extensionsIcons.js';54import { registerAction2, Action2, MenuId } from '../../../../platform/actions/common/actions.js';55import { IPaneComposite } from '../../../common/panecomposite.js';56import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';57import { coalesce } from '../../../../base/common/arrays.js';58import { extractEditorsAndFilesDropData } from '../../../../platform/dnd/browser/dnd.js';59import { extname } from '../../../../base/common/resources.js';60import { ILocalizedString } from '../../../../platform/action/common/action.js';61import { registerNavigableContainer } from '../../../browser/actions/widgetNavigationCommands.js';62import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';63import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';64import { SeverityIcon } from '../../../../base/browser/ui/severityIcon/severityIcon.js';65import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';66import { KeyCode } from '../../../../base/common/keyCodes.js';67import { ThemeIcon } from '../../../../base/common/themables.js';68import { Codicon } from '../../../../base/common/codicons.js';69import { IExtensionGalleryManifest, IExtensionGalleryManifestService, ExtensionGalleryManifestStatus } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';70import { URI } from '../../../../base/common/uri.js';71import { DEFAULT_ACCOUNT_SIGN_IN_COMMAND } from '../../../services/accounts/common/defaultAccount.js';7273export const ExtensionsSortByContext = new RawContextKey<string>('extensionsSortByValue', '');74export const SearchMarketplaceExtensionsContext = new RawContextKey<boolean>('searchMarketplaceExtensions', false);75export const SearchHasTextContext = new RawContextKey<boolean>('extensionSearchHasText', false);76const InstalledExtensionsContext = new RawContextKey<boolean>('installedExtensions', false);77const SearchInstalledExtensionsContext = new RawContextKey<boolean>('searchInstalledExtensions', false);78const SearchRecentlyUpdatedExtensionsContext = new RawContextKey<boolean>('searchRecentlyUpdatedExtensions', false);79const SearchExtensionUpdatesContext = new RawContextKey<boolean>('searchExtensionUpdates', false);80const SearchOutdatedExtensionsContext = new RawContextKey<boolean>('searchOutdatedExtensions', false);81const SearchEnabledExtensionsContext = new RawContextKey<boolean>('searchEnabledExtensions', false);82const SearchDisabledExtensionsContext = new RawContextKey<boolean>('searchDisabledExtensions', false);83const HasInstalledExtensionsContext = new RawContextKey<boolean>('hasInstalledExtensions', true);84export const BuiltInExtensionsContext = new RawContextKey<boolean>('builtInExtensions', false);85const SearchBuiltInExtensionsContext = new RawContextKey<boolean>('searchBuiltInExtensions', false);86const SearchUnsupportedWorkspaceExtensionsContext = new RawContextKey<boolean>('searchUnsupportedWorkspaceExtensions', false);87const SearchDeprecatedExtensionsContext = new RawContextKey<boolean>('searchDeprecatedExtensions', false);88export const RecommendedExtensionsContext = new RawContextKey<boolean>('recommendedExtensions', false);89const SortByUpdateDateContext = new RawContextKey<boolean>('sortByUpdateDate', false);90export const ExtensionsSearchValueContext = new RawContextKey<string>('extensionsSearchValue', '');9192const REMOTE_CATEGORY: ILocalizedString = localize2({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote");9394export class ExtensionsViewletViewsContribution extends Disposable implements IWorkbenchContribution {9596private readonly container: ViewContainer;9798constructor(99@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,100@ILabelService private readonly labelService: ILabelService,101@IViewDescriptorService viewDescriptorService: IViewDescriptorService,102@IContextKeyService private readonly contextKeyService: IContextKeyService103) {104super();105106this.container = viewDescriptorService.getViewContainerById(VIEWLET_ID)!;107this.registerViews();108}109110private registerViews(): void {111const viewDescriptors: IViewDescriptor[] = [];112113/* Default views */114viewDescriptors.push(...this.createDefaultExtensionsViewDescriptors());115116/* Search views */117viewDescriptors.push(...this.createSearchExtensionsViewDescriptors());118119/* Recommendations views */120viewDescriptors.push(...this.createRecommendedExtensionsViewDescriptors());121122/* Built-in extensions views */123viewDescriptors.push(...this.createBuiltinExtensionsViewDescriptors());124125/* Trust Required extensions views */126viewDescriptors.push(...this.createUnsupportedWorkspaceExtensionsViewDescriptors());127128/* Other Local Filtered extensions views */129viewDescriptors.push(...this.createOtherLocalFilteredExtensionsViewDescriptors());130131132viewDescriptors.push({133id: 'workbench.views.extensions.marketplaceAccess',134name: localize2('marketPlace', "Marketplace"),135ctorDescriptor: new SyncDescriptor(class extends ViewPane {136public override shouldShowWelcome() {137return true;138}139}),140when: ContextKeyExpr.and(141ContextKeyExpr.or(142ContextKeyExpr.has('searchMarketplaceExtensions'), ContextKeyExpr.and(DefaultViewsContext)143),144ContextKeyExpr.or(CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn), CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.AccessDenied))145),146order: -1,147});148149const viewRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);150viewRegistry.registerViews(viewDescriptors, this.container);151152viewRegistry.registerViewWelcomeContent('workbench.views.extensions.marketplaceAccess', {153content: localize('sign in', "[Sign in to access Extensions Marketplace]({0})", `command:${DEFAULT_ACCOUNT_SIGN_IN_COMMAND}`),154when: CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn)155});156157viewRegistry.registerViewWelcomeContent('workbench.views.extensions.marketplaceAccess', {158content: localize('access denied', "Your account does not have access to the Extensions Marketplace. Please contact your administrator."),159when: CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.AccessDenied)160});161}162163private createDefaultExtensionsViewDescriptors(): IViewDescriptor[] {164const viewDescriptors: IViewDescriptor[] = [];165166/*167* Default installed extensions views - Shows all user installed extensions.168*/169const servers: IExtensionManagementServer[] = [];170if (this.extensionManagementServerService.localExtensionManagementServer) {171servers.push(this.extensionManagementServerService.localExtensionManagementServer);172}173if (this.extensionManagementServerService.remoteExtensionManagementServer) {174servers.push(this.extensionManagementServerService.remoteExtensionManagementServer);175}176if (this.extensionManagementServerService.webExtensionManagementServer) {177servers.push(this.extensionManagementServerService.webExtensionManagementServer);178}179const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => {180return servers.length > 1 ? `${server.label} - ${viewTitle}` : viewTitle;181};182let installedWebExtensionsContextChangeEvent = Event.None;183if (this.extensionManagementServerService.webExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {184const interestingContextKeys = new Set();185interestingContextKeys.add('hasInstalledWebExtensions');186installedWebExtensionsContextChangeEvent = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(interestingContextKeys));187}188const serverLabelChangeEvent = Event.any(this.labelService.onDidChangeFormatters, installedWebExtensionsContextChangeEvent);189for (const server of servers) {190const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server);191const onDidChangeTitle = Event.map<void, string>(serverLabelChangeEvent, () => getInstalledViewName());192const id = servers.length > 1 ? `workbench.views.extensions.${server.id}.installed` : `workbench.views.extensions.installed`;193/* Installed extensions view */194viewDescriptors.push({195id,196get name() {197return {198value: getInstalledViewName(),199original: getViewName('Installed', server)200};201},202weight: 100,203order: 1,204when: ContextKeyExpr.and(DefaultViewsContext),205ctorDescriptor: new SyncDescriptor(ServerInstalledExtensionsView, [{ server, flexibleHeight: true, onDidChangeTitle }]),206/* Installed extensions views shall not be allowed to hidden when there are more than one server */207canToggleVisibility: servers.length === 1208});209210if (server === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer) {211this._register(registerAction2(class InstallLocalExtensionsInRemoteAction2 extends Action2 {212constructor() {213super({214id: 'workbench.extensions.installLocalExtensions',215get title() {216return localize2('select and install local extensions', "Install Local Extensions in '{0}'...", server.label);217},218category: REMOTE_CATEGORY,219icon: installLocalInRemoteIcon,220f1: true,221menu: {222id: MenuId.ViewTitle,223when: ContextKeyExpr.equals('view', id),224group: 'navigation',225}226});227}228run(accessor: ServicesAccessor): Promise<void> {229return accessor.get(IInstantiationService).createInstance(InstallLocalExtensionsInRemoteAction).run();230}231}));232}233}234235if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {236this._register(registerAction2(class InstallRemoteExtensionsInLocalAction2 extends Action2 {237constructor() {238super({239id: 'workbench.extensions.actions.installLocalExtensionsInRemote',240title: localize2('install remote in local', 'Install Remote Extensions Locally...'),241category: REMOTE_CATEGORY,242f1: true243});244}245run(accessor: ServicesAccessor): Promise<void> {246return accessor.get(IInstantiationService).createInstance(InstallRemoteExtensionsInLocalAction, 'workbench.extensions.actions.installLocalExtensionsInRemote').run();247}248}));249}250251/*252* Default popular extensions view253* Separate view for popular extensions required as we need to show popular and recommended sections254* in the default view when there is no search text, and user has no installed extensions.255*/256viewDescriptors.push({257id: 'workbench.views.extensions.popular',258name: localize2('popularExtensions', "Popular"),259ctorDescriptor: new SyncDescriptor(DefaultPopularExtensionsView, [{ hideBadge: true }]),260when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.not('hasInstalledExtensions'), CONTEXT_HAS_GALLERY),261weight: 60,262order: 2,263canToggleVisibility: false264});265266/*267* Default recommended extensions view268* When user has installed extensions, this is shown along with the views for enabled & disabled extensions269* When user has no installed extensions, this is shown along with the view for popular extensions270*/271viewDescriptors.push({272id: 'extensions.recommendedList',273name: localize2('recommendedExtensions', "Recommended"),274ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView, [{ flexibleHeight: true }]),275when: ContextKeyExpr.and(DefaultViewsContext, SortByUpdateDateContext.negate(), ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand'), CONTEXT_HAS_GALLERY),276weight: 40,277order: 3,278canToggleVisibility: true279});280281/* Installed views shall be default in multi server window */282if (servers.length === 1) {283/*284* Default enabled extensions view - Shows all user installed enabled extensions.285* Hidden by default286*/287viewDescriptors.push({288id: 'workbench.views.extensions.enabled',289name: localize2('enabledExtensions', "Enabled"),290ctorDescriptor: new SyncDescriptor(EnabledExtensionsView, [{}]),291when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.has('hasInstalledExtensions')),292hideByDefault: true,293weight: 40,294order: 4,295canToggleVisibility: true296});297298/*299* Default disabled extensions view - Shows all disabled extensions.300* Hidden by default301*/302viewDescriptors.push({303id: 'workbench.views.extensions.disabled',304name: localize2('disabledExtensions', "Disabled"),305ctorDescriptor: new SyncDescriptor(DisabledExtensionsView, [{}]),306when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.has('hasInstalledExtensions')),307hideByDefault: true,308weight: 10,309order: 5,310canToggleVisibility: true311});312313}314315return viewDescriptors;316}317318private createSearchExtensionsViewDescriptors(): IViewDescriptor[] {319const viewDescriptors: IViewDescriptor[] = [];320321/*322* View used for searching Marketplace323*/324viewDescriptors.push({325id: 'workbench.views.extensions.marketplace',326name: localize2('marketPlace', "Marketplace"),327ctorDescriptor: new SyncDescriptor(SearchMarketplaceExtensionsView, [{}]),328when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions'), CONTEXT_HAS_GALLERY)329});330331/*332* View used for searching all installed extensions333*/334viewDescriptors.push({335id: 'workbench.views.extensions.searchInstalled',336name: localize2('installed', "Installed"),337ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),338when: ContextKeyExpr.or(ContextKeyExpr.has('searchInstalledExtensions'), ContextKeyExpr.has('installedExtensions')),339});340341/*342* View used for searching recently updated extensions343*/344viewDescriptors.push({345id: 'workbench.views.extensions.searchRecentlyUpdated',346name: localize2('recently updated', "Recently Updated"),347ctorDescriptor: new SyncDescriptor(RecentlyUpdatedExtensionsView, [{}]),348when: ContextKeyExpr.or(SearchExtensionUpdatesContext, ContextKeyExpr.has('searchRecentlyUpdatedExtensions')),349order: 2,350});351352/*353* View used for searching enabled extensions354*/355viewDescriptors.push({356id: 'workbench.views.extensions.searchEnabled',357name: localize2('enabled', "Enabled"),358ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),359when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')),360});361362/*363* View used for searching disabled extensions364*/365viewDescriptors.push({366id: 'workbench.views.extensions.searchDisabled',367name: localize2('disabled', "Disabled"),368ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),369when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')),370});371372/*373* View used for searching outdated extensions374*/375viewDescriptors.push({376id: OUTDATED_EXTENSIONS_VIEW_ID,377name: localize2('availableUpdates', "Available Updates"),378ctorDescriptor: new SyncDescriptor(OutdatedExtensionsView, [{}]),379when: ContextKeyExpr.or(SearchExtensionUpdatesContext, ContextKeyExpr.has('searchOutdatedExtensions')),380order: 1,381});382383/*384* View used for searching builtin extensions385*/386viewDescriptors.push({387id: 'workbench.views.extensions.searchBuiltin',388name: localize2('builtin', "Builtin"),389ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),390when: ContextKeyExpr.and(ContextKeyExpr.has('searchBuiltInExtensions')),391});392393/*394* View used for searching workspace unsupported extensions395*/396viewDescriptors.push({397id: 'workbench.views.extensions.searchWorkspaceUnsupported',398name: localize2('workspaceUnsupported', "Workspace Unsupported"),399ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),400when: ContextKeyExpr.and(ContextKeyExpr.has('searchWorkspaceUnsupportedExtensions')),401});402403return viewDescriptors;404}405406private createRecommendedExtensionsViewDescriptors(): IViewDescriptor[] {407const viewDescriptors: IViewDescriptor[] = [];408409viewDescriptors.push({410id: WORKSPACE_RECOMMENDATIONS_VIEW_ID,411name: localize2('workspaceRecommendedExtensions', "Workspace Recommendations"),412ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView, [{}]),413when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), WorkbenchStateContext.notEqualsTo('empty')),414order: 1415});416417viewDescriptors.push({418id: 'workbench.views.extensions.otherRecommendations',419name: localize2('otherRecommendedExtensions', "Other Recommendations"),420ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView, [{}]),421when: ContextKeyExpr.has('recommendedExtensions'),422order: 2423});424425return viewDescriptors;426}427428private createBuiltinExtensionsViewDescriptors(): IViewDescriptor[] {429const viewDescriptors: IViewDescriptor[] = [];430431const configuredCategories = ['themes', 'programming languages'];432const otherCategories = EXTENSION_CATEGORIES.filter(c => !configuredCategories.includes(c.toLowerCase()));433otherCategories.push(NONE_CATEGORY);434const otherCategoriesQuery = `${otherCategories.map(c => `category:"${c}"`).join(' ')} ${configuredCategories.map(c => `category:"-${c}"`).join(' ')}`;435viewDescriptors.push({436id: 'workbench.views.extensions.builtinFeatureExtensions',437name: localize2('builtinFeatureExtensions', "Features"),438ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin ${otherCategoriesQuery}` }]),439when: ContextKeyExpr.has('builtInExtensions'),440});441442viewDescriptors.push({443id: 'workbench.views.extensions.builtinThemeExtensions',444name: localize2('builtInThemesExtensions', "Themes"),445ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin category:themes` }]),446when: ContextKeyExpr.has('builtInExtensions'),447});448449viewDescriptors.push({450id: 'workbench.views.extensions.builtinProgrammingLanguageExtensions',451name: localize2('builtinProgrammingLanguageExtensions', "Programming Languages"),452ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin category:"programming languages"` }]),453when: ContextKeyExpr.has('builtInExtensions'),454});455456return viewDescriptors;457}458459private createUnsupportedWorkspaceExtensionsViewDescriptors(): IViewDescriptor[] {460const viewDescriptors: IViewDescriptor[] = [];461462viewDescriptors.push({463id: 'workbench.views.extensions.untrustedUnsupportedExtensions',464name: localize2('untrustedUnsupportedExtensions', "Disabled in Restricted Mode"),465ctorDescriptor: new SyncDescriptor(UntrustedWorkspaceUnsupportedExtensionsView, [{}]),466when: ContextKeyExpr.and(SearchUnsupportedWorkspaceExtensionsContext),467});468469viewDescriptors.push({470id: 'workbench.views.extensions.untrustedPartiallySupportedExtensions',471name: localize2('untrustedPartiallySupportedExtensions', "Limited in Restricted Mode"),472ctorDescriptor: new SyncDescriptor(UntrustedWorkspacePartiallySupportedExtensionsView, [{}]),473when: ContextKeyExpr.and(SearchUnsupportedWorkspaceExtensionsContext),474});475476viewDescriptors.push({477id: 'workbench.views.extensions.virtualUnsupportedExtensions',478name: localize2('virtualUnsupportedExtensions', "Disabled in Virtual Workspaces"),479ctorDescriptor: new SyncDescriptor(VirtualWorkspaceUnsupportedExtensionsView, [{}]),480when: ContextKeyExpr.and(VirtualWorkspaceContext, SearchUnsupportedWorkspaceExtensionsContext),481});482483viewDescriptors.push({484id: 'workbench.views.extensions.virtualPartiallySupportedExtensions',485name: localize2('virtualPartiallySupportedExtensions', "Limited in Virtual Workspaces"),486ctorDescriptor: new SyncDescriptor(VirtualWorkspacePartiallySupportedExtensionsView, [{}]),487when: ContextKeyExpr.and(VirtualWorkspaceContext, SearchUnsupportedWorkspaceExtensionsContext),488});489490return viewDescriptors;491}492493private createOtherLocalFilteredExtensionsViewDescriptors(): IViewDescriptor[] {494const viewDescriptors: IViewDescriptor[] = [];495496viewDescriptors.push({497id: 'workbench.views.extensions.deprecatedExtensions',498name: localize2('deprecated', "Deprecated"),499ctorDescriptor: new SyncDescriptor(DeprecatedExtensionsView, [{}]),500when: ContextKeyExpr.and(SearchDeprecatedExtensionsContext),501});502503return viewDescriptors;504}505506}507508export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer {509510private readonly extensionsSearchValueContextKey: IContextKey<string>;511private readonly defaultViewsContextKey: IContextKey<boolean>;512private readonly sortByContextKey: IContextKey<string>;513private readonly searchMarketplaceExtensionsContextKey: IContextKey<boolean>;514private readonly searchMcpServersContextKey: IContextKey<boolean>;515private readonly searchHasTextContextKey: IContextKey<boolean>;516private readonly sortByUpdateDateContextKey: IContextKey<boolean>;517private readonly installedExtensionsContextKey: IContextKey<boolean>;518private readonly searchInstalledExtensionsContextKey: IContextKey<boolean>;519private readonly searchRecentlyUpdatedExtensionsContextKey: IContextKey<boolean>;520private readonly searchExtensionUpdatesContextKey: IContextKey<boolean>;521private readonly searchOutdatedExtensionsContextKey: IContextKey<boolean>;522private readonly searchEnabledExtensionsContextKey: IContextKey<boolean>;523private readonly searchDisabledExtensionsContextKey: IContextKey<boolean>;524private readonly hasInstalledExtensionsContextKey: IContextKey<boolean>;525private readonly builtInExtensionsContextKey: IContextKey<boolean>;526private readonly searchBuiltInExtensionsContextKey: IContextKey<boolean>;527private readonly searchWorkspaceUnsupportedExtensionsContextKey: IContextKey<boolean>;528private readonly searchDeprecatedExtensionsContextKey: IContextKey<boolean>;529private readonly recommendedExtensionsContextKey: IContextKey<boolean>;530531private searchDelayer: Delayer<void>;532private root: HTMLElement | undefined;533private header: HTMLElement | undefined;534private searchBox: SuggestEnabledInput | undefined;535private notificationContainer: HTMLElement | undefined;536private readonly searchViewletState: MementoObject;537private extensionGalleryManifest: IExtensionGalleryManifest | null = null;538539constructor(540@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,541@ITelemetryService telemetryService: ITelemetryService,542@IProgressService private readonly progressService: IProgressService,543@IInstantiationService instantiationService: IInstantiationService,544@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,545@IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,546@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,547@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,548@INotificationService private readonly notificationService: INotificationService,549@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,550@IThemeService themeService: IThemeService,551@IConfigurationService configurationService: IConfigurationService,552@IStorageService storageService: IStorageService,553@IWorkspaceContextService contextService: IWorkspaceContextService,554@IContextKeyService private readonly contextKeyService: IContextKeyService,555@IContextMenuService contextMenuService: IContextMenuService,556@IExtensionService extensionService: IExtensionService,557@IViewDescriptorService viewDescriptorService: IViewDescriptorService,558@IPreferencesService private readonly preferencesService: IPreferencesService,559@ICommandService private readonly commandService: ICommandService,560@ILogService logService: ILogService,561) {562super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, logService);563564this.searchDelayer = new Delayer(500);565this.extensionsSearchValueContextKey = ExtensionsSearchValueContext.bindTo(contextKeyService);566this.defaultViewsContextKey = DefaultViewsContext.bindTo(contextKeyService);567this.sortByContextKey = ExtensionsSortByContext.bindTo(contextKeyService);568this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService);569this.searchMcpServersContextKey = SearchMcpServersContext.bindTo(contextKeyService);570this.searchHasTextContextKey = SearchHasTextContext.bindTo(contextKeyService);571this.sortByUpdateDateContextKey = SortByUpdateDateContext.bindTo(contextKeyService);572this.installedExtensionsContextKey = InstalledExtensionsContext.bindTo(contextKeyService);573this.searchInstalledExtensionsContextKey = SearchInstalledExtensionsContext.bindTo(contextKeyService);574this.searchRecentlyUpdatedExtensionsContextKey = SearchRecentlyUpdatedExtensionsContext.bindTo(contextKeyService);575this.searchExtensionUpdatesContextKey = SearchExtensionUpdatesContext.bindTo(contextKeyService);576this.searchWorkspaceUnsupportedExtensionsContextKey = SearchUnsupportedWorkspaceExtensionsContext.bindTo(contextKeyService);577this.searchDeprecatedExtensionsContextKey = SearchDeprecatedExtensionsContext.bindTo(contextKeyService);578this.searchOutdatedExtensionsContextKey = SearchOutdatedExtensionsContext.bindTo(contextKeyService);579this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService);580this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService);581this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService);582this.builtInExtensionsContextKey = BuiltInExtensionsContext.bindTo(contextKeyService);583this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService);584this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService);585this._register(this.paneCompositeService.onDidPaneCompositeOpen(e => { if (e.viewContainerLocation === ViewContainerLocation.Sidebar) { this.onViewletOpen(e.composite); } }, this));586this._register(extensionsWorkbenchService.onReset(() => this.refresh()));587this.searchViewletState = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE);588589extensionGalleryManifestService.getExtensionGalleryManifest()590.then(galleryManifest => {591this.extensionGalleryManifest = galleryManifest;592this._register(extensionGalleryManifestService.onDidChangeExtensionGalleryManifest(galleryManifest => {593this.extensionGalleryManifest = galleryManifest;594this.refresh();595}));596});597}598599get searchValue(): string | undefined {600return this.searchBox?.getValue();601}602603override create(parent: HTMLElement): void {604parent.classList.add('extensions-viewlet');605this.root = parent;606607const overlay = append(this.root, $('.overlay'));608const overlayBackgroundColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';609overlay.style.backgroundColor = overlayBackgroundColor;610hide(overlay);611612this.header = append(this.root, $('.header'));613const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");614615const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';616617const searchContainer = append(this.header, $('.extensions-search-container'));618619this.searchBox = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, searchContainer, {620triggerCharacters: ['@'],621sortKey: (item: string) => {622if (item.indexOf(':') === -1) { return 'a'; }623else if (/ext:/.test(item) || /id:/.test(item) || /tag:/.test(item)) { return 'b'; }624else if (/sort:/.test(item)) { return 'c'; }625else { return 'd'; }626},627provideResults: (query: string) => Query.suggestions(query, this.extensionGalleryManifest)628}, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue }));629630this.notificationContainer = append(this.header, $('.notification-container.hidden', { 'tabindex': '0' }));631this.renderNotificaiton();632this._register(this.extensionsWorkbenchService.onDidChangeExtensionsNotification(() => this.renderNotificaiton()));633634this.updateInstalledExtensionsContexts();635if (this.searchBox.getValue()) {636this.triggerSearch();637}638639this._register(this.searchBox.onInputDidChange(() => {640this.sortByContextKey.set(Query.parse(this.searchBox?.getValue() ?? '').sortBy);641this.triggerSearch();642}, this));643644this._register(this.searchBox.onShouldFocusResults(() => this.focusListView(), this));645646const controlElement = append(searchContainer, $('.extensions-search-actions-container'));647this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, controlElement, extensionsSearchActionsMenu, {648toolbarOptions: {649primaryGroup: () => true,650},651actionViewItemProvider: (action, options) => createActionViewItem(this.instantiationService, action, options)652}));653654// Register DragAndDrop support655this._register(new DragAndDropObserver(this.root, {656onDragEnter: (e: DragEvent) => {657if (this.isSupportedDragElement(e)) {658show(overlay);659}660},661onDragLeave: (e: DragEvent) => {662if (this.isSupportedDragElement(e)) {663hide(overlay);664}665},666onDragOver: (e: DragEvent) => {667if (this.isSupportedDragElement(e)) {668e.dataTransfer!.dropEffect = 'copy';669}670},671onDrop: async (e: DragEvent) => {672if (this.isSupportedDragElement(e)) {673hide(overlay);674675const vsixs = coalesce((await this.instantiationService.invokeFunction(accessor => extractEditorsAndFilesDropData(accessor, e)))676.map(editor => editor.resource && extname(editor.resource) === '.vsix' ? editor.resource : undefined));677678if (vsixs.length > 0) {679try {680// Attempt to install the extension(s)681await this.commandService.executeCommand(INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, vsixs);682}683catch (err) {684this.notificationService.error(err);685}686}687}688}689}));690691super.create(append(this.root, $('.extensions')));692693const focusTracker = this._register(trackFocus(this.root));694const isSearchBoxFocused = () => this.searchBox?.inputWidget.hasWidgetFocus();695this._register(registerNavigableContainer({696name: 'extensionsView',697focusNotifiers: [focusTracker],698focusNextWidget: () => {699if (isSearchBoxFocused()) {700this.focusListView();701}702},703focusPreviousWidget: () => {704if (!isSearchBoxFocused()) {705this.searchBox?.focus();706}707}708}));709}710711override focus(): void {712super.focus();713this.searchBox?.focus();714}715716private _dimension: Dimension | undefined;717override layout(dimension: Dimension): void {718this._dimension = dimension;719if (this.root) {720this.root.classList.toggle('narrow', dimension.width <= 250);721this.root.classList.toggle('mini', dimension.width <= 200);722}723this.searchBox?.layout(new Dimension(dimension.width - 34 - /*padding*/8 - (24 * 2), 20));724const searchBoxHeight = 20 + 21 /*margin*/;725const headerHeight = this.header && !!this.notificationContainer?.childNodes.length ? this.notificationContainer.clientHeight + searchBoxHeight + 10 /*margin*/ : searchBoxHeight;726this.header!.style.height = `${headerHeight}px`;727super.layout(new Dimension(dimension.width, dimension.height - headerHeight));728}729730override getOptimalWidth(): number {731return 400;732}733734search(value: string): void {735if (this.searchBox && this.searchBox.getValue() !== value) {736this.searchBox.setValue(value);737}738}739740async refresh(): Promise<void> {741await this.updateInstalledExtensionsContexts();742this.doSearch(true);743if (this.configurationService.getValue(AutoCheckUpdatesConfigurationKey)) {744this.extensionsWorkbenchService.checkForUpdates();745}746}747748private readonly notificationDisposables = this._register(new MutableDisposable<DisposableStore>());749private renderNotificaiton(): void {750if (!this.notificationContainer) {751return;752}753754clearNode(this.notificationContainer);755this.notificationDisposables.value = new DisposableStore();756const status = this.extensionsWorkbenchService.getExtensionsNotification();757const query = status?.extensions.map(extension => `@id:${extension.identifier.id}`).join(' ');758if (status && (query === this.searchBox?.getValue() || !this.searchMarketplaceExtensionsContextKey.get())) {759this.notificationContainer.setAttribute('aria-label', status.message);760this.notificationContainer.classList.remove('hidden');761const messageContainer = append(this.notificationContainer, $('.message-container'));762append(messageContainer, $('span')).className = SeverityIcon.className(status.severity);763append(messageContainer, $('span.message', undefined, status.message));764const showAction = append(messageContainer,765$('span.message-text-action', {766'tabindex': '0',767'role': 'button',768'aria-label': `${status.message}. ${localize('click show', "Click to Show")}`769}, localize('show', "Show")));770this.notificationDisposables.value.add(addDisposableListener(showAction, EventType.CLICK, () => this.search(query ?? '')));771this.notificationDisposables.value.add(addDisposableListener(showAction, EventType.KEY_DOWN, (e: KeyboardEvent) => {772const standardKeyboardEvent = new StandardKeyboardEvent(e);773if (standardKeyboardEvent.keyCode === KeyCode.Enter || standardKeyboardEvent.keyCode === KeyCode.Space) {774this.search(query ?? '');775}776standardKeyboardEvent.stopPropagation();777}));778const dismissAction = append(this.notificationContainer,779$(`span.message-action${ThemeIcon.asCSSSelector(Codicon.close)}`, {780'tabindex': '0',781'role': 'button',782'aria-label': localize('dismiss', "Dismiss"),783'title': localize('dismiss', "Dismiss")784}));785this.notificationDisposables.value.add(addDisposableListener(dismissAction, EventType.CLICK, () => status.dismiss()));786this.notificationDisposables.value.add(addDisposableListener(dismissAction, EventType.KEY_DOWN, (e: KeyboardEvent) => {787const standardKeyboardEvent = new StandardKeyboardEvent(e);788if (standardKeyboardEvent.keyCode === KeyCode.Enter || standardKeyboardEvent.keyCode === KeyCode.Space) {789status.dismiss();790}791standardKeyboardEvent.stopPropagation();792}));793} else {794this.notificationContainer.removeAttribute('aria-label');795this.notificationContainer.classList.add('hidden');796}797798if (this._dimension) {799this.layout(this._dimension);800}801}802803private async updateInstalledExtensionsContexts(): Promise<void> {804const result = await this.extensionsWorkbenchService.queryLocal();805this.hasInstalledExtensionsContextKey.set(result.some(r => !r.isBuiltin));806}807808private triggerSearch(): void {809this.searchDelayer.trigger(() => this.doSearch(), this.searchBox && this.searchBox.getValue() ? 500 : 0).then(undefined, err => this.onError(err));810}811812private normalizedQuery(): string {813return this.searchBox814? this.searchBox.getValue()815.trim()816.replace(/@category/g, 'category')817.replace(/@tag:/g, 'tag:')818.replace(/@ext:/g, 'ext:')819.replace(/@featured/g, 'featured')820.replace(/@popular/g, this.extensionManagementServerService.webExtensionManagementServer && !this.extensionManagementServerService.localExtensionManagementServer && !this.extensionManagementServerService.remoteExtensionManagementServer ? '@web' : '@popular')821: '';822}823824protected override saveState(): void {825const value = this.searchBox ? this.searchBox.getValue() : '';826if (ExtensionsListView.isLocalExtensionsQuery(value)) {827this.searchViewletState['query.value'] = value;828} else {829this.searchViewletState['query.value'] = '';830}831super.saveState();832}833834private doSearch(refresh?: boolean): Promise<void> {835const value = this.normalizedQuery();836this.contextKeyService.bufferChangeEvents(() => {837const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value);838this.searchHasTextContextKey.set(value.trim() !== '');839this.extensionsSearchValueContextKey.set(value);840this.installedExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value));841this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isSearchInstalledExtensionsQuery(value));842this.searchRecentlyUpdatedExtensionsContextKey.set(ExtensionsListView.isSearchRecentlyUpdatedQuery(value) && !ExtensionsListView.isSearchExtensionUpdatesQuery(value));843this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value) && !ExtensionsListView.isSearchExtensionUpdatesQuery(value));844this.searchExtensionUpdatesContextKey.set(ExtensionsListView.isSearchExtensionUpdatesQuery(value));845this.searchEnabledExtensionsContextKey.set(ExtensionsListView.isEnabledExtensionsQuery(value));846this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value));847this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isSearchBuiltInExtensionsQuery(value));848this.searchWorkspaceUnsupportedExtensionsContextKey.set(ExtensionsListView.isSearchWorkspaceUnsupportedExtensionsQuery(value));849this.searchDeprecatedExtensionsContextKey.set(ExtensionsListView.isSearchDeprecatedExtensionsQuery(value));850this.builtInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));851this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery);852this.searchMcpServersContextKey.set(!!value && /@mcp\s?.*/i.test(value));853this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery && !this.searchMcpServersContextKey.get());854this.sortByUpdateDateContextKey.set(ExtensionsListView.isSortUpdateDateQuery(value));855this.defaultViewsContextKey.set(!value || ExtensionsListView.isSortInstalledExtensionsQuery(value));856});857858this.renderNotificaiton();859860return this.showExtensionsViews(this.panes);861}862863protected override onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] {864const addedViews = super.onDidAddViewDescriptors(added);865this.showExtensionsViews(addedViews);866return addedViews;867}868869private async showExtensionsViews(views: ViewPane[]): Promise<void> {870await this.progress(Promise.all(views.map(async view => {871if (view instanceof AbstractExtensionsListView) {872const model = await view.show(this.normalizedQuery());873this.alertSearchResult(model.length, view.id);874}875})));876}877878private alertSearchResult(count: number, viewId: string): void {879const view = this.viewContainerModel.visibleViewDescriptors.find(view => view.id === viewId);880switch (count) {881case 0:882break;883case 1:884if (view) {885alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", view.name.value));886} else {887alert(localize('extensionFound', "1 extension found."));888}889break;890default:891if (view) {892alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, view.name.value));893} else {894alert(localize('extensionsFound', "{0} extensions found.", count));895}896break;897}898}899900private getFirstExpandedPane(): ExtensionsListView | undefined {901for (const pane of this.panes) {902if (pane.isExpanded() && pane instanceof ExtensionsListView) {903return pane;904}905}906return undefined;907}908909private focusListView(): void {910const pane = this.getFirstExpandedPane();911if (pane && pane.count() > 0) {912pane.focus();913}914}915916private onViewletOpen(viewlet: IPaneComposite): void {917if (!viewlet || viewlet.getId() === VIEWLET_ID) {918return;919}920921if (this.configurationService.getValue<boolean>(CloseExtensionDetailsOnViewChangeKey)) {922const promises = this.editorGroupService.groups.map(group => {923const editors = group.editors.filter(input => input instanceof ExtensionsInput);924925return group.closeEditors(editors);926});927928Promise.all(promises);929}930}931932private progress<T>(promise: Promise<T>): Promise<T> {933return this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => promise);934}935936private onError(err: Error): void {937if (isCancellationError(err)) {938return;939}940941const message = err && err.message || '';942943if (/ECONNREFUSED/.test(message)) {944const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), [945new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())946]);947948this.notificationService.error(error);949return;950}951952this.notificationService.error(err);953}954955private isSupportedDragElement(e: DragEvent): boolean {956if (e.dataTransfer) {957const typesLowerCase = e.dataTransfer.types.map(t => t.toLocaleLowerCase());958return typesLowerCase.indexOf('files') !== -1;959}960961return false;962}963}964965export class StatusUpdater extends Disposable implements IWorkbenchContribution {966967private readonly badgeHandle = this._register(new MutableDisposable());968969constructor(970@IActivityService private readonly activityService: IActivityService,971@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,972@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,973@IConfigurationService private readonly configurationService: IConfigurationService974) {975super();976this.onServiceChange();977this._register(Event.any(Event.debounce(extensionsWorkbenchService.onChange, () => undefined, 100, undefined, undefined, undefined, this._store), extensionsWorkbenchService.onDidChangeExtensionsNotification)(this.onServiceChange, this));978}979980private onServiceChange(): void {981this.badgeHandle.clear();982let badge: IBadge | undefined;983984const extensionsNotification = this.extensionsWorkbenchService.getExtensionsNotification();985if (extensionsNotification) {986if (extensionsNotification.severity === Severity.Warning) {987badge = new WarningBadge(() => extensionsNotification.message);988}989}990991else {992const actionRequired = this.configurationService.getValue(AutoRestartConfigurationKey) === true ? [] : this.extensionsWorkbenchService.installed.filter(e => e.runtimeState !== undefined);993const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) && !actionRequired.includes(e) ? 1 : 0), 0);994const newBadgeNumber = outdated + actionRequired.length;995if (newBadgeNumber > 0) {996let msg = '';997if (outdated) {998msg += outdated === 1 ? localize('extensionToUpdate', '{0} requires update', outdated) : localize('extensionsToUpdate', '{0} require update', outdated);999}1000if (outdated > 0 && actionRequired.length > 0) {1001msg += ', ';1002}1003if (actionRequired.length) {1004msg += actionRequired.length === 1 ? localize('extensionToReload', '{0} requires restart', actionRequired.length) : localize('extensionsToReload', '{0} require restart', actionRequired.length);1005}1006badge = new NumberBadge(newBadgeNumber, () => msg);1007}1008}10091010if (badge) {1011this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge });1012}1013}1014}10151016export class MaliciousExtensionChecker implements IWorkbenchContribution {10171018constructor(1019@IExtensionManagementService private readonly extensionsManagementService: IExtensionManagementService,1020@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,1021@IHostService private readonly hostService: IHostService,1022@ILogService private readonly logService: ILogService,1023@INotificationService private readonly notificationService: INotificationService,1024@ICommandService private readonly commandService: ICommandService,1025) {1026this.loopCheckForMaliciousExtensions();1027}10281029private loopCheckForMaliciousExtensions(): void {1030this.checkForMaliciousExtensions()1031.then(() => timeout(1000 * 60 * 5)) // every five minutes1032.then(() => this.loopCheckForMaliciousExtensions());1033}10341035private async checkForMaliciousExtensions(): Promise<void> {1036try {1037const maliciousExtensions: [ILocalExtension, string | undefined][] = [];1038let shouldRestartExtensions = false;1039let shouldReloadWindow = false;1040for (const extension of this.extensionsWorkbenchService.installed) {1041if (extension.isMalicious && extension.local) {1042maliciousExtensions.push([extension.local, extension.maliciousInfoLink]);1043shouldRestartExtensions = shouldRestartExtensions || extension.runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions;1044shouldReloadWindow = shouldReloadWindow || extension.runtimeState?.action === ExtensionRuntimeActionType.ReloadWindow;1045}1046}1047if (maliciousExtensions.length) {1048await this.extensionsManagementService.uninstallExtensions(maliciousExtensions.map(e => ({ extension: e[0], options: { remove: true } })));1049for (const [extension, link] of maliciousExtensions) {1050const buttons: IPromptChoice[] = [];1051if (shouldRestartExtensions || shouldReloadWindow) {1052buttons.push({1053label: shouldRestartExtensions ? localize('restartNow', "Restart Extensions") : localize('reloadNow', "Reload Now"),1054run: () => shouldRestartExtensions ? this.extensionsWorkbenchService.updateRunningExtensions() : this.hostService.reload()1055});1056}1057if (link) {1058buttons.push({1059label: localize('learnMore', "Learn More"),1060run: () => this.commandService.executeCommand('vscode.open', URI.parse(link))1061});1062}1063this.notificationService.prompt(1064Severity.Warning,1065localize('malicious warning', "The extension '{0}' was found to be problematic and has been uninstalled", extension.manifest.displayName || extension.identifier.id),1066buttons,1067{1068sticky: true,1069priority: NotificationPriority.URGENT1070}1071);1072}1073}10741075} catch (err) {1076this.logService.error(err);1077}1078}1079}10801081export class ExtensionMarketplaceStatusUpdater extends Disposable implements IWorkbenchContribution {10821083private readonly badgeHandle = this._register(new MutableDisposable());1084private readonly accountBadgeDisposable = this._register(new MutableDisposable());10851086constructor(1087@IActivityService private readonly activityService: IActivityService,1088@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService1089) {1090super();1091this.updateBadge();1092this._register(this.extensionGalleryManifestService.onDidChangeExtensionGalleryManifestStatus(() => this.updateBadge()));1093}10941095private async updateBadge(): Promise<void> {1096this.badgeHandle.clear();10971098const status = this.extensionGalleryManifestService.extensionGalleryManifestStatus;1099let badge: IBadge | undefined;11001101switch (status) {1102case ExtensionGalleryManifestStatus.RequiresSignIn:1103badge = new NumberBadge(1, () => localize('signInRequired', "Sign in required to access marketplace"));1104break;1105case ExtensionGalleryManifestStatus.AccessDenied:1106badge = new WarningBadge(() => localize('accessDenied', "Access denied to marketplace"));1107break;1108}11091110if (badge) {1111this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge });1112}11131114this.accountBadgeDisposable.clear();1115if (status === ExtensionGalleryManifestStatus.RequiresSignIn) {1116const badge = new NumberBadge(1, () => localize('sign in enterprise marketplace', "Sign in to access Marketplace"));1117this.accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge });1118}1119}1120}112111221123