Path: blob/main/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
5241 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 { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';48import { IPreferencesService } from '../../../services/preferences/common/preferences.js';49import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js';50import { VirtualWorkspaceContext, WorkbenchStateContext } from '../../../common/contextkeys.js';51import { ICommandService } from '../../../../platform/commands/common/commands.js';52import { installLocalInRemoteIcon } from './extensionsIcons.js';53import { registerAction2, Action2, MenuId } from '../../../../platform/actions/common/actions.js';54import { IPaneComposite } from '../../../common/panecomposite.js';55import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';56import { coalesce } from '../../../../base/common/arrays.js';57import { extractEditorsAndFilesDropData } from '../../../../platform/dnd/browser/dnd.js';58import { extname } from '../../../../base/common/resources.js';59import { ILocalizedString } from '../../../../platform/action/common/action.js';60import { registerNavigableContainer } from '../../../browser/actions/widgetNavigationCommands.js';61import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';62import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';63import { SeverityIcon } from '../../../../base/browser/ui/severityIcon/severityIcon.js';64import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';65import { KeyCode } from '../../../../base/common/keyCodes.js';66import { ThemeIcon } from '../../../../base/common/themables.js';67import { Codicon } from '../../../../base/common/codicons.js';68import { IExtensionGalleryManifest, IExtensionGalleryManifestService, ExtensionGalleryManifestStatus } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';69import { URI } from '../../../../base/common/uri.js';70import { DEFAULT_ACCOUNT_SIGN_IN_COMMAND } from '../../../services/accounts/browser/defaultAccount.js';71import { IHoverService } from '../../../../platform/hover/browser/hover.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");9394interface IExtensionsViewletState {95'query.value'?: string;96}9798export class ExtensionsViewletViewsContribution extends Disposable implements IWorkbenchContribution {99100private readonly container: ViewContainer;101102constructor(103@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,104@ILabelService private readonly labelService: ILabelService,105@IViewDescriptorService viewDescriptorService: IViewDescriptorService,106@IContextKeyService private readonly contextKeyService: IContextKeyService107) {108super();109110this.container = viewDescriptorService.getViewContainerById(VIEWLET_ID)!;111this.registerViews();112}113114private registerViews(): void {115const viewDescriptors: IViewDescriptor[] = [];116117/* Default views */118viewDescriptors.push(...this.createDefaultExtensionsViewDescriptors());119120/* Search views */121viewDescriptors.push(...this.createSearchExtensionsViewDescriptors());122123/* Recommendations views */124viewDescriptors.push(...this.createRecommendedExtensionsViewDescriptors());125126/* Built-in extensions views */127viewDescriptors.push(...this.createBuiltinExtensionsViewDescriptors());128129/* Trust Required extensions views */130viewDescriptors.push(...this.createUnsupportedWorkspaceExtensionsViewDescriptors());131132/* Other Local Filtered extensions views */133viewDescriptors.push(...this.createOtherLocalFilteredExtensionsViewDescriptors());134135136viewDescriptors.push({137id: 'workbench.views.extensions.marketplaceAccess',138name: localize2('marketPlace', "Marketplace"),139ctorDescriptor: new SyncDescriptor(class extends ViewPane {140public override shouldShowWelcome() {141return true;142}143}),144when: ContextKeyExpr.and(145ContextKeyExpr.or(146ContextKeyExpr.has('searchMarketplaceExtensions'), ContextKeyExpr.and(DefaultViewsContext)147),148ContextKeyExpr.or(CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn), CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.AccessDenied))149),150order: -1,151});152153const viewRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);154viewRegistry.registerViews(viewDescriptors, this.container);155156viewRegistry.registerViewWelcomeContent('workbench.views.extensions.marketplaceAccess', {157content: localize('sign in', "[Sign in to access Extensions Marketplace]({0})", `command:${DEFAULT_ACCOUNT_SIGN_IN_COMMAND}`),158when: CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn)159});160161viewRegistry.registerViewWelcomeContent('workbench.views.extensions.marketplaceAccess', {162content: localize('access denied', "Your account does not have access to the Extensions Marketplace. Please contact your administrator."),163when: CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.AccessDenied)164});165}166167private createDefaultExtensionsViewDescriptors(): IViewDescriptor[] {168const viewDescriptors: IViewDescriptor[] = [];169170/*171* Default installed extensions views - Shows all user installed extensions.172*/173const servers: IExtensionManagementServer[] = [];174if (this.extensionManagementServerService.localExtensionManagementServer) {175servers.push(this.extensionManagementServerService.localExtensionManagementServer);176}177if (this.extensionManagementServerService.remoteExtensionManagementServer) {178servers.push(this.extensionManagementServerService.remoteExtensionManagementServer);179}180if (this.extensionManagementServerService.webExtensionManagementServer) {181servers.push(this.extensionManagementServerService.webExtensionManagementServer);182}183const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => {184return servers.length > 1 ? `${server.label} - ${viewTitle}` : viewTitle;185};186let installedWebExtensionsContextChangeEvent = Event.None;187if (this.extensionManagementServerService.webExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {188const interestingContextKeys = new Set();189interestingContextKeys.add('hasInstalledWebExtensions');190installedWebExtensionsContextChangeEvent = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(interestingContextKeys));191}192const serverLabelChangeEvent = Event.any(this.labelService.onDidChangeFormatters, installedWebExtensionsContextChangeEvent);193for (const server of servers) {194const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server);195const onDidChangeTitle = Event.map<void, string>(serverLabelChangeEvent, () => getInstalledViewName());196const id = servers.length > 1 ? `workbench.views.extensions.${server.id}.installed` : `workbench.views.extensions.installed`;197/* Installed extensions view */198viewDescriptors.push({199id,200get name() {201return {202value: getInstalledViewName(),203original: getViewName('Installed', server)204};205},206weight: 100,207order: 1,208when: ContextKeyExpr.and(DefaultViewsContext),209ctorDescriptor: new SyncDescriptor(ServerInstalledExtensionsView, [{ server, flexibleHeight: true, onDidChangeTitle }]),210/* Installed extensions views shall not be allowed to hidden when there are more than one server */211canToggleVisibility: servers.length === 1212});213214if (server === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer) {215this._register(registerAction2(class InstallLocalExtensionsInRemoteAction2 extends Action2 {216constructor() {217super({218id: 'workbench.extensions.installLocalExtensions',219get title() {220return localize2('select and install local extensions', "Install Local Extensions in '{0}'...", server.label);221},222category: REMOTE_CATEGORY,223icon: installLocalInRemoteIcon,224f1: true,225menu: {226id: MenuId.ViewTitle,227when: ContextKeyExpr.equals('view', id),228group: 'navigation',229}230});231}232run(accessor: ServicesAccessor): Promise<void> {233return accessor.get(IInstantiationService).createInstance(InstallLocalExtensionsInRemoteAction).run();234}235}));236}237}238239if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {240this._register(registerAction2(class InstallRemoteExtensionsInLocalAction2 extends Action2 {241constructor() {242super({243id: 'workbench.extensions.actions.installLocalExtensionsInRemote',244title: localize2('install remote in local', 'Install Remote Extensions Locally...'),245category: REMOTE_CATEGORY,246f1: true247});248}249run(accessor: ServicesAccessor): Promise<void> {250return accessor.get(IInstantiationService).createInstance(InstallRemoteExtensionsInLocalAction, 'workbench.extensions.actions.installLocalExtensionsInRemote').run();251}252}));253}254255/*256* Default popular extensions view257* Separate view for popular extensions required as we need to show popular and recommended sections258* in the default view when there is no search text, and user has no installed extensions.259*/260viewDescriptors.push({261id: 'workbench.views.extensions.popular',262name: localize2('popularExtensions', "Popular"),263ctorDescriptor: new SyncDescriptor(DefaultPopularExtensionsView, [{ hideBadge: true }]),264when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.not('hasInstalledExtensions'), CONTEXT_HAS_GALLERY),265weight: 60,266order: 2,267canToggleVisibility: false268});269270/*271* Default recommended extensions view272* When user has installed extensions, this is shown along with the views for enabled & disabled extensions273* When user has no installed extensions, this is shown along with the view for popular extensions274*/275viewDescriptors.push({276id: 'extensions.recommendedList',277name: localize2('recommendedExtensions', "Recommended"),278ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView, [{ flexibleHeight: true }]),279when: ContextKeyExpr.and(DefaultViewsContext, SortByUpdateDateContext.negate(), ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand'), CONTEXT_HAS_GALLERY),280weight: 40,281order: 3,282canToggleVisibility: true283});284285/* Installed views shall be default in multi server window */286if (servers.length === 1) {287/*288* Default enabled extensions view - Shows all user installed enabled extensions.289* Hidden by default290*/291viewDescriptors.push({292id: 'workbench.views.extensions.enabled',293name: localize2('enabledExtensions', "Enabled"),294ctorDescriptor: new SyncDescriptor(EnabledExtensionsView, [{}]),295when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.has('hasInstalledExtensions')),296hideByDefault: true,297weight: 40,298order: 4,299canToggleVisibility: true300});301302/*303* Default disabled extensions view - Shows all disabled extensions.304* Hidden by default305*/306viewDescriptors.push({307id: 'workbench.views.extensions.disabled',308name: localize2('disabledExtensions', "Disabled"),309ctorDescriptor: new SyncDescriptor(DisabledExtensionsView, [{}]),310when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.has('hasInstalledExtensions')),311hideByDefault: true,312weight: 10,313order: 5,314canToggleVisibility: true315});316317}318319return viewDescriptors;320}321322private createSearchExtensionsViewDescriptors(): IViewDescriptor[] {323const viewDescriptors: IViewDescriptor[] = [];324325/*326* View used for searching Marketplace327*/328viewDescriptors.push({329id: 'workbench.views.extensions.marketplace',330name: localize2('marketPlace', "Marketplace"),331ctorDescriptor: new SyncDescriptor(SearchMarketplaceExtensionsView, [{}]),332when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions'), CONTEXT_HAS_GALLERY)333});334335/*336* View used for searching all installed extensions337*/338viewDescriptors.push({339id: 'workbench.views.extensions.searchInstalled',340name: localize2('installed', "Installed"),341ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),342when: ContextKeyExpr.or(ContextKeyExpr.has('searchInstalledExtensions'), ContextKeyExpr.has('installedExtensions')),343});344345/*346* View used for searching recently updated extensions347*/348viewDescriptors.push({349id: 'workbench.views.extensions.searchRecentlyUpdated',350name: localize2('recently updated', "Recently Updated"),351ctorDescriptor: new SyncDescriptor(RecentlyUpdatedExtensionsView, [{}]),352when: ContextKeyExpr.or(SearchExtensionUpdatesContext, ContextKeyExpr.has('searchRecentlyUpdatedExtensions')),353order: 2,354});355356/*357* View used for searching enabled extensions358*/359viewDescriptors.push({360id: 'workbench.views.extensions.searchEnabled',361name: localize2('enabled', "Enabled"),362ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),363when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')),364});365366/*367* View used for searching disabled extensions368*/369viewDescriptors.push({370id: 'workbench.views.extensions.searchDisabled',371name: localize2('disabled', "Disabled"),372ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),373when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')),374});375376/*377* View used for searching outdated extensions378*/379viewDescriptors.push({380id: OUTDATED_EXTENSIONS_VIEW_ID,381name: localize2('availableUpdates', "Available Updates"),382ctorDescriptor: new SyncDescriptor(OutdatedExtensionsView, [{}]),383when: ContextKeyExpr.or(SearchExtensionUpdatesContext, ContextKeyExpr.has('searchOutdatedExtensions')),384order: 1,385});386387/*388* View used for searching builtin extensions389*/390viewDescriptors.push({391id: 'workbench.views.extensions.searchBuiltin',392name: localize2('builtin', "Builtin"),393ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),394when: ContextKeyExpr.and(ContextKeyExpr.has('searchBuiltInExtensions')),395});396397/*398* View used for searching workspace unsupported extensions399*/400viewDescriptors.push({401id: 'workbench.views.extensions.searchWorkspaceUnsupported',402name: localize2('workspaceUnsupported', "Workspace Unsupported"),403ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),404when: ContextKeyExpr.and(ContextKeyExpr.has('searchWorkspaceUnsupportedExtensions')),405});406407return viewDescriptors;408}409410private createRecommendedExtensionsViewDescriptors(): IViewDescriptor[] {411const viewDescriptors: IViewDescriptor[] = [];412413viewDescriptors.push({414id: WORKSPACE_RECOMMENDATIONS_VIEW_ID,415name: localize2('workspaceRecommendedExtensions', "Workspace Recommendations"),416ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView, [{}]),417when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), WorkbenchStateContext.notEqualsTo('empty')),418order: 1419});420421viewDescriptors.push({422id: 'workbench.views.extensions.otherRecommendations',423name: localize2('otherRecommendedExtensions', "Other Recommendations"),424ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView, [{}]),425when: ContextKeyExpr.has('recommendedExtensions'),426order: 2427});428429return viewDescriptors;430}431432private createBuiltinExtensionsViewDescriptors(): IViewDescriptor[] {433const viewDescriptors: IViewDescriptor[] = [];434435const configuredCategories = ['themes', 'programming languages'];436const otherCategories = EXTENSION_CATEGORIES.filter(c => !configuredCategories.includes(c.toLowerCase()));437otherCategories.push(NONE_CATEGORY);438const otherCategoriesQuery = `${otherCategories.map(c => `category:"${c}"`).join(' ')} ${configuredCategories.map(c => `category:"-${c}"`).join(' ')}`;439viewDescriptors.push({440id: 'workbench.views.extensions.builtinFeatureExtensions',441name: localize2('builtinFeatureExtensions', "Features"),442ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin ${otherCategoriesQuery}` }]),443when: ContextKeyExpr.has('builtInExtensions'),444});445446viewDescriptors.push({447id: 'workbench.views.extensions.builtinThemeExtensions',448name: localize2('builtInThemesExtensions', "Themes"),449ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin category:themes` }]),450when: ContextKeyExpr.has('builtInExtensions'),451});452453viewDescriptors.push({454id: 'workbench.views.extensions.builtinProgrammingLanguageExtensions',455name: localize2('builtinProgrammingLanguageExtensions', "Programming Languages"),456ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin category:"programming languages"` }]),457when: ContextKeyExpr.has('builtInExtensions'),458});459460return viewDescriptors;461}462463private createUnsupportedWorkspaceExtensionsViewDescriptors(): IViewDescriptor[] {464const viewDescriptors: IViewDescriptor[] = [];465466viewDescriptors.push({467id: 'workbench.views.extensions.untrustedUnsupportedExtensions',468name: localize2('untrustedUnsupportedExtensions', "Disabled in Restricted Mode"),469ctorDescriptor: new SyncDescriptor(UntrustedWorkspaceUnsupportedExtensionsView, [{}]),470when: ContextKeyExpr.and(SearchUnsupportedWorkspaceExtensionsContext),471});472473viewDescriptors.push({474id: 'workbench.views.extensions.untrustedPartiallySupportedExtensions',475name: localize2('untrustedPartiallySupportedExtensions', "Limited in Restricted Mode"),476ctorDescriptor: new SyncDescriptor(UntrustedWorkspacePartiallySupportedExtensionsView, [{}]),477when: ContextKeyExpr.and(SearchUnsupportedWorkspaceExtensionsContext),478});479480viewDescriptors.push({481id: 'workbench.views.extensions.virtualUnsupportedExtensions',482name: localize2('virtualUnsupportedExtensions', "Disabled in Virtual Workspaces"),483ctorDescriptor: new SyncDescriptor(VirtualWorkspaceUnsupportedExtensionsView, [{}]),484when: ContextKeyExpr.and(VirtualWorkspaceContext, SearchUnsupportedWorkspaceExtensionsContext),485});486487viewDescriptors.push({488id: 'workbench.views.extensions.virtualPartiallySupportedExtensions',489name: localize2('virtualPartiallySupportedExtensions', "Limited in Virtual Workspaces"),490ctorDescriptor: new SyncDescriptor(VirtualWorkspacePartiallySupportedExtensionsView, [{}]),491when: ContextKeyExpr.and(VirtualWorkspaceContext, SearchUnsupportedWorkspaceExtensionsContext),492});493494return viewDescriptors;495}496497private createOtherLocalFilteredExtensionsViewDescriptors(): IViewDescriptor[] {498const viewDescriptors: IViewDescriptor[] = [];499500viewDescriptors.push({501id: 'workbench.views.extensions.deprecatedExtensions',502name: localize2('deprecated', "Deprecated"),503ctorDescriptor: new SyncDescriptor(DeprecatedExtensionsView, [{}]),504when: ContextKeyExpr.and(SearchDeprecatedExtensionsContext),505});506507return viewDescriptors;508}509510}511512export class ExtensionsViewPaneContainer extends ViewPaneContainer<IExtensionsViewletState> implements IExtensionsViewPaneContainer {513514private readonly extensionsSearchValueContextKey: IContextKey<string>;515private readonly defaultViewsContextKey: IContextKey<boolean>;516private readonly sortByContextKey: IContextKey<string>;517private readonly searchMarketplaceExtensionsContextKey: IContextKey<boolean>;518private readonly searchMcpServersContextKey: IContextKey<boolean>;519private readonly searchHasTextContextKey: IContextKey<boolean>;520private readonly sortByUpdateDateContextKey: IContextKey<boolean>;521private readonly installedExtensionsContextKey: IContextKey<boolean>;522private readonly searchInstalledExtensionsContextKey: IContextKey<boolean>;523private readonly searchRecentlyUpdatedExtensionsContextKey: IContextKey<boolean>;524private readonly searchExtensionUpdatesContextKey: IContextKey<boolean>;525private readonly searchOutdatedExtensionsContextKey: IContextKey<boolean>;526private readonly searchEnabledExtensionsContextKey: IContextKey<boolean>;527private readonly searchDisabledExtensionsContextKey: IContextKey<boolean>;528private readonly hasInstalledExtensionsContextKey: IContextKey<boolean>;529private readonly builtInExtensionsContextKey: IContextKey<boolean>;530private readonly searchBuiltInExtensionsContextKey: IContextKey<boolean>;531private readonly searchWorkspaceUnsupportedExtensionsContextKey: IContextKey<boolean>;532private readonly searchDeprecatedExtensionsContextKey: IContextKey<boolean>;533private readonly recommendedExtensionsContextKey: IContextKey<boolean>;534535private searchDelayer: Delayer<void>;536private root: HTMLElement | undefined;537private header: HTMLElement | undefined;538private searchBox: SuggestEnabledInput | undefined;539private notificationContainer: HTMLElement | undefined;540private readonly searchViewletState: IExtensionsViewletState;541private extensionGalleryManifest: IExtensionGalleryManifest | null = null;542543constructor(544@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,545@ITelemetryService telemetryService: ITelemetryService,546@IProgressService private readonly progressService: IProgressService,547@IInstantiationService instantiationService: IInstantiationService,548@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,549@IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,550@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,551@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,552@INotificationService private readonly notificationService: INotificationService,553@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,554@IThemeService themeService: IThemeService,555@IConfigurationService configurationService: IConfigurationService,556@IStorageService storageService: IStorageService,557@IWorkspaceContextService contextService: IWorkspaceContextService,558@IContextKeyService private readonly contextKeyService: IContextKeyService,559@IContextMenuService contextMenuService: IContextMenuService,560@IExtensionService extensionService: IExtensionService,561@IViewDescriptorService viewDescriptorService: IViewDescriptorService,562@IPreferencesService private readonly preferencesService: IPreferencesService,563@ICommandService private readonly commandService: ICommandService,564@ILogService logService: ILogService,565@IHoverService private readonly hoverService: IHoverService,566) {567super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, logService);568569this.searchDelayer = new Delayer(500);570this.extensionsSearchValueContextKey = ExtensionsSearchValueContext.bindTo(contextKeyService);571this.defaultViewsContextKey = DefaultViewsContext.bindTo(contextKeyService);572this.sortByContextKey = ExtensionsSortByContext.bindTo(contextKeyService);573this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService);574this.searchMcpServersContextKey = SearchMcpServersContext.bindTo(contextKeyService);575this.searchHasTextContextKey = SearchHasTextContext.bindTo(contextKeyService);576this.sortByUpdateDateContextKey = SortByUpdateDateContext.bindTo(contextKeyService);577this.installedExtensionsContextKey = InstalledExtensionsContext.bindTo(contextKeyService);578this.searchInstalledExtensionsContextKey = SearchInstalledExtensionsContext.bindTo(contextKeyService);579this.searchRecentlyUpdatedExtensionsContextKey = SearchRecentlyUpdatedExtensionsContext.bindTo(contextKeyService);580this.searchExtensionUpdatesContextKey = SearchExtensionUpdatesContext.bindTo(contextKeyService);581this.searchWorkspaceUnsupportedExtensionsContextKey = SearchUnsupportedWorkspaceExtensionsContext.bindTo(contextKeyService);582this.searchDeprecatedExtensionsContextKey = SearchDeprecatedExtensionsContext.bindTo(contextKeyService);583this.searchOutdatedExtensionsContextKey = SearchOutdatedExtensionsContext.bindTo(contextKeyService);584this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService);585this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService);586this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService);587this.builtInExtensionsContextKey = BuiltInExtensionsContext.bindTo(contextKeyService);588this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService);589this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService);590this._register(this.paneCompositeService.onDidPaneCompositeOpen(e => { if (e.viewContainerLocation === ViewContainerLocation.Sidebar) { this.onViewletOpen(e.composite); } }, this));591this._register(extensionsWorkbenchService.onReset(() => this.refresh()));592this.searchViewletState = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE);593594extensionGalleryManifestService.getExtensionGalleryManifest()595.then(galleryManifest => {596this.extensionGalleryManifest = galleryManifest;597this._register(extensionGalleryManifestService.onDidChangeExtensionGalleryManifest(galleryManifest => {598this.extensionGalleryManifest = galleryManifest;599this.refresh();600}));601});602}603604get searchValue(): string | undefined {605return this.searchBox?.getValue();606}607608override create(parent: HTMLElement): void {609parent.classList.add('extensions-viewlet');610this.root = parent;611612const overlay = append(this.root, $('.overlay'));613const overlayBackgroundColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';614overlay.style.backgroundColor = overlayBackgroundColor;615hide(overlay);616617this.header = append(this.root, $('.header'));618const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");619620const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';621622const searchContainer = append(this.header, $('.extensions-search-container'));623624this.searchBox = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, searchContainer, {625triggerCharacters: ['@'],626sortKey: (item: string) => {627if (item.indexOf(':') === -1) { return 'a'; }628else if (/ext:/.test(item) || /id:/.test(item) || /tag:/.test(item)) { return 'b'; }629else if (/sort:/.test(item)) { return 'c'; }630else { return 'd'; }631},632provideResults: (query: string) => Query.suggestions(query, this.extensionGalleryManifest)633}, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue }));634635this.notificationContainer = append(this.header, $('.notification-container.hidden', { 'tabindex': '0' }));636this.renderNotificaiton();637this._register(this.extensionsWorkbenchService.onDidChangeExtensionsNotification(() => this.renderNotificaiton()));638639this.updateInstalledExtensionsContexts();640if (this.searchBox.getValue()) {641this.triggerSearch();642}643644this._register(this.searchBox.onInputDidChange(() => {645this.sortByContextKey.set(Query.parse(this.searchBox?.getValue() ?? '').sortBy);646this.triggerSearch();647}, this));648649this._register(this.searchBox.onShouldFocusResults(() => this.focusListView(), this));650651const controlElement = append(searchContainer, $('.extensions-search-actions-container'));652this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, controlElement, extensionsSearchActionsMenu, {653toolbarOptions: {654primaryGroup: () => true,655},656actionViewItemProvider: (action, options) => createActionViewItem(this.instantiationService, action, options)657}));658659// Register DragAndDrop support660this._register(new DragAndDropObserver(this.root, {661onDragEnter: (e: DragEvent) => {662if (this.isSupportedDragElement(e)) {663show(overlay);664}665},666onDragLeave: (e: DragEvent) => {667if (this.isSupportedDragElement(e)) {668hide(overlay);669}670},671onDragOver: (e: DragEvent) => {672if (this.isSupportedDragElement(e)) {673e.dataTransfer!.dropEffect = 'copy';674}675},676onDrop: async (e: DragEvent) => {677if (this.isSupportedDragElement(e)) {678hide(overlay);679680const vsixs = coalesce((await this.instantiationService.invokeFunction(accessor => extractEditorsAndFilesDropData(accessor, e)))681.map(editor => editor.resource && extname(editor.resource) === '.vsix' ? editor.resource : undefined));682683if (vsixs.length > 0) {684try {685// Attempt to install the extension(s)686await this.commandService.executeCommand(INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, vsixs);687}688catch (err) {689this.notificationService.error(err);690}691}692}693}694}));695696super.create(append(this.root, $('.extensions')));697698const focusTracker = this._register(trackFocus(this.root));699const isSearchBoxFocused = () => this.searchBox?.inputWidget.hasWidgetFocus();700this._register(registerNavigableContainer({701name: 'extensionsView',702focusNotifiers: [focusTracker],703focusNextWidget: () => {704if (isSearchBoxFocused()) {705this.focusListView();706}707},708focusPreviousWidget: () => {709if (!isSearchBoxFocused()) {710this.searchBox?.focus();711}712}713}));714}715716override focus(): void {717super.focus();718this.searchBox?.focus();719}720721private _dimension: Dimension | undefined;722override layout(dimension: Dimension): void {723this._dimension = dimension;724if (this.root) {725this.root.classList.toggle('narrow', dimension.width <= 250);726this.root.classList.toggle('mini', dimension.width <= 200);727}728this.searchBox?.layout(new Dimension(dimension.width - 34 - /*padding*/8 - (24 * 2), 20));729const searchBoxHeight = 20 + 21 /*margin*/;730const headerHeight = this.header && !!this.notificationContainer?.childNodes.length ? this.notificationContainer.clientHeight + searchBoxHeight + 10 /*margin*/ : searchBoxHeight;731this.header!.style.height = `${headerHeight}px`;732super.layout(new Dimension(dimension.width, dimension.height - headerHeight));733}734735override getOptimalWidth(): number {736return 400;737}738739search(value: string): void {740if (this.searchBox && this.searchBox.getValue() !== value) {741this.searchBox.setValue(value);742}743}744745async refresh(): Promise<void> {746await this.updateInstalledExtensionsContexts();747this.doSearch(true);748if (this.configurationService.getValue(AutoCheckUpdatesConfigurationKey)) {749this.extensionsWorkbenchService.checkForUpdates();750}751}752753private readonly notificationDisposables = this._register(new MutableDisposable<DisposableStore>());754private renderNotificaiton(): void {755if (!this.notificationContainer) {756return;757}758759clearNode(this.notificationContainer);760this.notificationDisposables.value = new DisposableStore();761const status = this.extensionsWorkbenchService.getExtensionsNotification();762const query = status?.extensions.map(extension => `@id:${extension.identifier.id}`).join(' ');763if (status && (query === this.searchBox?.getValue() || !this.searchMarketplaceExtensionsContextKey.get())) {764this.notificationContainer.setAttribute('aria-label', status.message);765this.notificationContainer.classList.remove('hidden');766const messageContainer = append(this.notificationContainer, $('.message-container'));767append(messageContainer, $('span')).className = SeverityIcon.className(status.severity);768append(messageContainer, $('span.message', undefined, status.message));769const showAction = append(messageContainer,770$('span.message-text-action', {771'tabindex': '0',772'role': 'button',773'aria-label': `${status.message}. ${localize('click show', "Click to Show")}`774}, localize('show', "Show")));775this.notificationDisposables.value.add(addDisposableListener(showAction, EventType.CLICK, () => this.search(query ?? '')));776this.notificationDisposables.value.add(addDisposableListener(showAction, EventType.KEY_DOWN, (e: KeyboardEvent) => {777const standardKeyboardEvent = new StandardKeyboardEvent(e);778if (standardKeyboardEvent.keyCode === KeyCode.Enter || standardKeyboardEvent.keyCode === KeyCode.Space) {779this.search(query ?? '');780}781standardKeyboardEvent.stopPropagation();782}));783const dismissAction = append(this.notificationContainer,784$(`span.message-action${ThemeIcon.asCSSSelector(Codicon.close)}`, {785'tabindex': '0',786'role': 'button',787'aria-label': localize('dismiss', "Dismiss"),788}));789this.notificationDisposables.value.add(this.hoverService.setupDelayedHover(dismissAction, { content: localize('dismiss hover', "Dismiss") }));790this.notificationDisposables.value.add(addDisposableListener(dismissAction, EventType.CLICK, () => status.dismiss()));791this.notificationDisposables.value.add(addDisposableListener(dismissAction, EventType.KEY_DOWN, (e: KeyboardEvent) => {792const standardKeyboardEvent = new StandardKeyboardEvent(e);793if (standardKeyboardEvent.keyCode === KeyCode.Enter || standardKeyboardEvent.keyCode === KeyCode.Space) {794status.dismiss();795}796standardKeyboardEvent.stopPropagation();797}));798} else {799this.notificationContainer.removeAttribute('aria-label');800this.notificationContainer.classList.add('hidden');801}802803if (this._dimension) {804this.layout(this._dimension);805}806}807808private async updateInstalledExtensionsContexts(): Promise<void> {809const result = await this.extensionsWorkbenchService.queryLocal();810this.hasInstalledExtensionsContextKey.set(result.some(r => !r.isBuiltin));811}812813private triggerSearch(): void {814this.searchDelayer.trigger(() => this.doSearch(), this.searchBox && this.searchBox.getValue() ? 500 : 0).then(undefined, err => this.onError(err));815}816817private normalizedQuery(): string {818return this.searchBox819? this.searchBox.getValue()820.trim()821.replace(/@category/g, 'category')822.replace(/@tag:/g, 'tag:')823.replace(/@ext:/g, 'ext:')824.replace(/@featured/g, 'featured')825.replace(/@popular/g, this.extensionManagementServerService.webExtensionManagementServer && !this.extensionManagementServerService.localExtensionManagementServer && !this.extensionManagementServerService.remoteExtensionManagementServer ? '@web' : '@popular')826: '';827}828829protected override saveState(): void {830const value = this.searchBox ? this.searchBox.getValue() : '';831if (ExtensionsListView.isLocalExtensionsQuery(value)) {832this.searchViewletState['query.value'] = value;833} else {834this.searchViewletState['query.value'] = '';835}836super.saveState();837}838839private doSearch(refresh?: boolean): Promise<void> {840const value = this.normalizedQuery();841this.contextKeyService.bufferChangeEvents(() => {842const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value);843this.searchHasTextContextKey.set(value.trim() !== '');844this.extensionsSearchValueContextKey.set(value);845this.installedExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value));846this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isSearchInstalledExtensionsQuery(value));847this.searchRecentlyUpdatedExtensionsContextKey.set(ExtensionsListView.isSearchRecentlyUpdatedQuery(value) && !ExtensionsListView.isSearchExtensionUpdatesQuery(value));848this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value) && !ExtensionsListView.isSearchExtensionUpdatesQuery(value));849this.searchExtensionUpdatesContextKey.set(ExtensionsListView.isSearchExtensionUpdatesQuery(value));850this.searchEnabledExtensionsContextKey.set(ExtensionsListView.isEnabledExtensionsQuery(value));851this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value));852this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isSearchBuiltInExtensionsQuery(value));853this.searchWorkspaceUnsupportedExtensionsContextKey.set(ExtensionsListView.isSearchWorkspaceUnsupportedExtensionsQuery(value));854this.searchDeprecatedExtensionsContextKey.set(ExtensionsListView.isSearchDeprecatedExtensionsQuery(value));855this.builtInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));856this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery);857this.searchMcpServersContextKey.set(!!value && /@mcp\s?.*/i.test(value));858this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery && !this.searchMcpServersContextKey.get());859this.sortByUpdateDateContextKey.set(ExtensionsListView.isSortUpdateDateQuery(value));860this.defaultViewsContextKey.set(!value || ExtensionsListView.isSortInstalledExtensionsQuery(value));861});862863this.renderNotificaiton();864865return this.showExtensionsViews(this.panes);866}867868protected override onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] {869const addedViews = super.onDidAddViewDescriptors(added);870this.showExtensionsViews(addedViews);871return addedViews;872}873874private async showExtensionsViews(views: ViewPane[]): Promise<void> {875await this.progress(Promise.all(views.map(async view => {876if (view instanceof AbstractExtensionsListView) {877const model = await view.show(this.normalizedQuery());878this.alertSearchResult(model.length, view.id);879}880})));881}882883private alertSearchResult(count: number, viewId: string): void {884const view = this.viewContainerModel.visibleViewDescriptors.find(view => view.id === viewId);885switch (count) {886case 0:887break;888case 1:889if (view) {890alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", view.name.value));891} else {892alert(localize('extensionFound', "1 extension found."));893}894break;895default:896if (view) {897alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, view.name.value));898} else {899alert(localize('extensionsFound', "{0} extensions found.", count));900}901break;902}903}904905private getFirstExpandedPane(): ExtensionsListView | undefined {906for (const pane of this.panes) {907if (pane.isExpanded() && pane instanceof ExtensionsListView) {908return pane;909}910}911return undefined;912}913914private focusListView(): void {915const pane = this.getFirstExpandedPane();916if (pane && pane.count() > 0) {917pane.focus();918}919}920921private onViewletOpen(viewlet: IPaneComposite): void {922if (!viewlet || viewlet.getId() === VIEWLET_ID) {923return;924}925926if (this.configurationService.getValue<boolean>(CloseExtensionDetailsOnViewChangeKey)) {927const promises = this.editorGroupService.groups.map(group => {928const editors = group.editors.filter(input => input instanceof ExtensionsInput);929930return group.closeEditors(editors);931});932933Promise.all(promises);934}935}936937private progress<T>(promise: Promise<T>): Promise<T> {938return this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => promise);939}940941private onError(err: Error): void {942if (isCancellationError(err)) {943return;944}945946const message = err && err.message || '';947948if (/ECONNREFUSED/.test(message)) {949const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), [950new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())951]);952953this.notificationService.error(error);954return;955}956957this.notificationService.error(err);958}959960private isSupportedDragElement(e: DragEvent): boolean {961if (e.dataTransfer) {962const typesLowerCase = e.dataTransfer.types.map(t => t.toLocaleLowerCase());963return typesLowerCase.indexOf('files') !== -1;964}965966return false;967}968}969970export class StatusUpdater extends Disposable implements IWorkbenchContribution {971972private readonly badgeHandle = this._register(new MutableDisposable());973974constructor(975@IActivityService private readonly activityService: IActivityService,976@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,977@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,978@IConfigurationService private readonly configurationService: IConfigurationService979) {980super();981this.onServiceChange();982this._register(Event.any(Event.debounce(extensionsWorkbenchService.onChange, () => undefined, 100, undefined, undefined, undefined, this._store), extensionsWorkbenchService.onDidChangeExtensionsNotification)(this.onServiceChange, this));983}984985private onServiceChange(): void {986this.badgeHandle.clear();987let badge: IBadge | undefined;988989const extensionsNotification = this.extensionsWorkbenchService.getExtensionsNotification();990if (extensionsNotification) {991if (extensionsNotification.severity === Severity.Warning) {992badge = new WarningBadge(() => extensionsNotification.message);993}994}995996else {997const actionRequired = this.configurationService.getValue(AutoRestartConfigurationKey) === true ? [] : this.extensionsWorkbenchService.installed.filter(e => e.runtimeState !== undefined);998const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) && !actionRequired.includes(e) ? 1 : 0), 0);999const newBadgeNumber = outdated + actionRequired.length;1000if (newBadgeNumber > 0) {1001let msg = '';1002if (outdated) {1003msg += outdated === 1 ? localize('extensionToUpdate', '{0} requires update', outdated) : localize('extensionsToUpdate', '{0} require update', outdated);1004}1005if (outdated > 0 && actionRequired.length > 0) {1006msg += ', ';1007}1008if (actionRequired.length) {1009msg += actionRequired.length === 1 ? localize('extensionToReload', '{0} requires restart', actionRequired.length) : localize('extensionsToReload', '{0} require restart', actionRequired.length);1010}1011badge = new NumberBadge(newBadgeNumber, () => msg);1012}1013}10141015if (badge) {1016this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge });1017}1018}1019}10201021export class MaliciousExtensionChecker implements IWorkbenchContribution {10221023constructor(1024@IExtensionManagementService private readonly extensionsManagementService: IExtensionManagementService,1025@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,1026@IHostService private readonly hostService: IHostService,1027@ILogService private readonly logService: ILogService,1028@INotificationService private readonly notificationService: INotificationService,1029@ICommandService private readonly commandService: ICommandService,1030) {1031this.loopCheckForMaliciousExtensions();1032}10331034private loopCheckForMaliciousExtensions(): void {1035this.checkForMaliciousExtensions()1036.then(() => timeout(1000 * 60 * 5)) // every five minutes1037.then(() => this.loopCheckForMaliciousExtensions());1038}10391040private async checkForMaliciousExtensions(): Promise<void> {1041try {1042const maliciousExtensions: [ILocalExtension, string | undefined][] = [];1043let shouldRestartExtensions = false;1044let shouldReloadWindow = false;1045for (const extension of this.extensionsWorkbenchService.installed) {1046if (extension.isMalicious && extension.local) {1047maliciousExtensions.push([extension.local, extension.maliciousInfoLink]);1048shouldRestartExtensions = shouldRestartExtensions || extension.runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions;1049shouldReloadWindow = shouldReloadWindow || extension.runtimeState?.action === ExtensionRuntimeActionType.ReloadWindow;1050}1051}1052if (maliciousExtensions.length) {1053await this.extensionsManagementService.uninstallExtensions(maliciousExtensions.map(e => ({ extension: e[0], options: { remove: true } })));1054for (const [extension, link] of maliciousExtensions) {1055const buttons: IPromptChoice[] = [];1056if (shouldRestartExtensions || shouldReloadWindow) {1057buttons.push({1058label: shouldRestartExtensions ? localize('restartNow', "Restart Extensions") : localize('reloadNow', "Reload Now"),1059run: () => shouldRestartExtensions ? this.extensionsWorkbenchService.updateRunningExtensions() : this.hostService.reload()1060});1061}1062if (link) {1063buttons.push({1064label: localize('learnMore', "Learn More"),1065run: () => this.commandService.executeCommand('vscode.open', URI.parse(link))1066});1067}1068this.notificationService.prompt(1069Severity.Warning,1070localize('malicious warning', "The extension '{0}' was found to be problematic and has been uninstalled", extension.manifest.displayName || extension.identifier.id),1071buttons,1072{1073sticky: true,1074priority: NotificationPriority.URGENT1075}1076);1077}1078}10791080} catch (err) {1081this.logService.error(err);1082}1083}1084}10851086export class ExtensionMarketplaceStatusUpdater extends Disposable implements IWorkbenchContribution {10871088private readonly badgeHandle = this._register(new MutableDisposable());1089private readonly accountBadgeDisposable = this._register(new MutableDisposable());10901091constructor(1092@IActivityService private readonly activityService: IActivityService,1093@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService1094) {1095super();1096this.updateBadge();1097this._register(this.extensionGalleryManifestService.onDidChangeExtensionGalleryManifestStatus(() => this.updateBadge()));1098}10991100private async updateBadge(): Promise<void> {1101this.badgeHandle.clear();11021103const status = this.extensionGalleryManifestService.extensionGalleryManifestStatus;1104let badge: IBadge | undefined;11051106switch (status) {1107case ExtensionGalleryManifestStatus.RequiresSignIn:1108badge = new NumberBadge(1, () => localize('signInRequired', "Sign in required to access marketplace"));1109break;1110case ExtensionGalleryManifestStatus.AccessDenied:1111badge = new WarningBadge(() => localize('accessDenied', "Access denied to marketplace"));1112break;1113}11141115if (badge) {1116this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge });1117}11181119this.accountBadgeDisposable.clear();1120if (status === ExtensionGalleryManifestStatus.RequiresSignIn) {1121const badge = new NumberBadge(1, () => localize('sign in enterprise marketplace', "Sign in to access Marketplace"));1122this.accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge });1123}1124}1125}112611271128