Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './media/extensionsViewlet.css';
7
import { localize, localize2 } from '../../../../nls.js';
8
import { timeout, Delayer } from '../../../../base/common/async.js';
9
import { isCancellationError } from '../../../../base/common/errors.js';
10
import { createErrorWithActions } from '../../../../base/common/errorMessage.js';
11
import { IWorkbenchContribution } from '../../../common/contributions.js';
12
import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
13
import { Event } from '../../../../base/common/event.js';
14
import { Action } from '../../../../base/common/actions.js';
15
import { append, $, Dimension, hide, show, DragAndDropObserver, trackFocus, addDisposableListener, EventType, clearNode } from '../../../../base/browser/dom.js';
16
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
17
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
18
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
19
import { 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';
20
import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAction } from './extensionsActions.js';
21
import { IExtensionManagementService, ILocalExtension } from '../../../../platform/extensionManagement/common/extensionManagement.js';
22
import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from '../../../services/extensionManagement/common/extensionManagement.js';
23
import { ExtensionsInput } from '../common/extensionsInput.js';
24
import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView, VirtualWorkspacePartiallySupportedExtensionsView, DefaultPopularExtensionsView, DeprecatedExtensionsView, SearchMarketplaceExtensionsView, RecentlyUpdatedExtensionsView, OutdatedExtensionsView, StaticQueryExtensionsView, NONE_CATEGORY, AbstractExtensionsListView } from './extensionsViews.js';
25
import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';
26
import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';
27
import Severity from '../../../../base/common/severity.js';
28
import { IActivityService, IBadge, NumberBadge, WarningBadge } from '../../../services/activity/common/activity.js';
29
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
30
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
31
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef, ViewContainerLocation } from '../../../common/views.js';
32
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
33
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
34
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from '../../../../platform/contextkey/common/contextkey.js';
35
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
36
import { ILogService } from '../../../../platform/log/common/log.js';
37
import { INotificationService, IPromptChoice, NotificationPriority } from '../../../../platform/notification/common/notification.js';
38
import { IHostService } from '../../../services/host/browser/host.js';
39
import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';
40
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
41
import { ViewPane } from '../../../browser/parts/views/viewPane.js';
42
import { Query } from '../common/extensionQuery.js';
43
import { SuggestEnabledInput } from '../../codeEditor/browser/suggestEnabledInput/suggestEnabledInput.js';
44
import { alert } from '../../../../base/browser/ui/aria/aria.js';
45
import { EXTENSION_CATEGORIES } from '../../../../platform/extensions/common/extensions.js';
46
import { Registry } from '../../../../platform/registry/common/platform.js';
47
import { ILabelService } from '../../../../platform/label/common/label.js';
48
import { MementoObject } from '../../../common/memento.js';
49
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
50
import { IPreferencesService } from '../../../services/preferences/common/preferences.js';
51
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js';
52
import { VirtualWorkspaceContext, WorkbenchStateContext } from '../../../common/contextkeys.js';
53
import { ICommandService } from '../../../../platform/commands/common/commands.js';
54
import { installLocalInRemoteIcon } from './extensionsIcons.js';
55
import { registerAction2, Action2, MenuId } from '../../../../platform/actions/common/actions.js';
56
import { IPaneComposite } from '../../../common/panecomposite.js';
57
import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';
58
import { coalesce } from '../../../../base/common/arrays.js';
59
import { extractEditorsAndFilesDropData } from '../../../../platform/dnd/browser/dnd.js';
60
import { extname } from '../../../../base/common/resources.js';
61
import { ILocalizedString } from '../../../../platform/action/common/action.js';
62
import { registerNavigableContainer } from '../../../browser/actions/widgetNavigationCommands.js';
63
import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';
64
import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
65
import { SeverityIcon } from '../../../../base/browser/ui/severityIcon/severityIcon.js';
66
import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
67
import { KeyCode } from '../../../../base/common/keyCodes.js';
68
import { ThemeIcon } from '../../../../base/common/themables.js';
69
import { Codicon } from '../../../../base/common/codicons.js';
70
import { IExtensionGalleryManifest, IExtensionGalleryManifestService, ExtensionGalleryManifestStatus } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';
71
import { URI } from '../../../../base/common/uri.js';
72
import { DEFAULT_ACCOUNT_SIGN_IN_COMMAND } from '../../../services/accounts/common/defaultAccount.js';
73
74
export const ExtensionsSortByContext = new RawContextKey<string>('extensionsSortByValue', '');
75
export const SearchMarketplaceExtensionsContext = new RawContextKey<boolean>('searchMarketplaceExtensions', false);
76
export const SearchHasTextContext = new RawContextKey<boolean>('extensionSearchHasText', false);
77
const InstalledExtensionsContext = new RawContextKey<boolean>('installedExtensions', false);
78
const SearchInstalledExtensionsContext = new RawContextKey<boolean>('searchInstalledExtensions', false);
79
const SearchRecentlyUpdatedExtensionsContext = new RawContextKey<boolean>('searchRecentlyUpdatedExtensions', false);
80
const SearchExtensionUpdatesContext = new RawContextKey<boolean>('searchExtensionUpdates', false);
81
const SearchOutdatedExtensionsContext = new RawContextKey<boolean>('searchOutdatedExtensions', false);
82
const SearchEnabledExtensionsContext = new RawContextKey<boolean>('searchEnabledExtensions', false);
83
const SearchDisabledExtensionsContext = new RawContextKey<boolean>('searchDisabledExtensions', false);
84
const HasInstalledExtensionsContext = new RawContextKey<boolean>('hasInstalledExtensions', true);
85
export const BuiltInExtensionsContext = new RawContextKey<boolean>('builtInExtensions', false);
86
const SearchBuiltInExtensionsContext = new RawContextKey<boolean>('searchBuiltInExtensions', false);
87
const SearchUnsupportedWorkspaceExtensionsContext = new RawContextKey<boolean>('searchUnsupportedWorkspaceExtensions', false);
88
const SearchDeprecatedExtensionsContext = new RawContextKey<boolean>('searchDeprecatedExtensions', false);
89
export const RecommendedExtensionsContext = new RawContextKey<boolean>('recommendedExtensions', false);
90
const SortByUpdateDateContext = new RawContextKey<boolean>('sortByUpdateDate', false);
91
export const ExtensionsSearchValueContext = new RawContextKey<string>('extensionsSearchValue', '');
92
93
const REMOTE_CATEGORY: ILocalizedString = localize2({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote");
94
95
export class ExtensionsViewletViewsContribution extends Disposable implements IWorkbenchContribution {
96
97
private readonly container: ViewContainer;
98
99
constructor(
100
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
101
@ILabelService private readonly labelService: ILabelService,
102
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
103
@IContextKeyService private readonly contextKeyService: IContextKeyService
104
) {
105
super();
106
107
this.container = viewDescriptorService.getViewContainerById(VIEWLET_ID)!;
108
this.registerViews();
109
}
110
111
private registerViews(): void {
112
const viewDescriptors: IViewDescriptor[] = [];
113
114
/* Default views */
115
viewDescriptors.push(...this.createDefaultExtensionsViewDescriptors());
116
117
/* Search views */
118
viewDescriptors.push(...this.createSearchExtensionsViewDescriptors());
119
120
/* Recommendations views */
121
viewDescriptors.push(...this.createRecommendedExtensionsViewDescriptors());
122
123
/* Built-in extensions views */
124
viewDescriptors.push(...this.createBuiltinExtensionsViewDescriptors());
125
126
/* Trust Required extensions views */
127
viewDescriptors.push(...this.createUnsupportedWorkspaceExtensionsViewDescriptors());
128
129
/* Other Local Filtered extensions views */
130
viewDescriptors.push(...this.createOtherLocalFilteredExtensionsViewDescriptors());
131
132
133
viewDescriptors.push({
134
id: 'workbench.views.extensions.marketplaceAccess',
135
name: localize2('marketPlace', "Marketplace"),
136
ctorDescriptor: new SyncDescriptor(class extends ViewPane {
137
public override shouldShowWelcome() {
138
return true;
139
}
140
}),
141
when: ContextKeyExpr.and(
142
ContextKeyExpr.or(
143
ContextKeyExpr.has('searchMarketplaceExtensions'), ContextKeyExpr.and(DefaultViewsContext)
144
),
145
ContextKeyExpr.or(CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn), CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.AccessDenied))
146
),
147
order: -1,
148
});
149
150
const viewRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
151
viewRegistry.registerViews(viewDescriptors, this.container);
152
153
viewRegistry.registerViewWelcomeContent('workbench.views.extensions.marketplaceAccess', {
154
content: localize('sign in', "[Sign in to access Extensions Marketplace]({0})", `command:${DEFAULT_ACCOUNT_SIGN_IN_COMMAND}`),
155
when: CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn)
156
});
157
158
viewRegistry.registerViewWelcomeContent('workbench.views.extensions.marketplaceAccess', {
159
content: localize('access denied', "Your account does not have access to the Extensions Marketplace. Please contact your administrator."),
160
when: CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.AccessDenied)
161
});
162
}
163
164
private createDefaultExtensionsViewDescriptors(): IViewDescriptor[] {
165
const viewDescriptors: IViewDescriptor[] = [];
166
167
/*
168
* Default installed extensions views - Shows all user installed extensions.
169
*/
170
const servers: IExtensionManagementServer[] = [];
171
if (this.extensionManagementServerService.localExtensionManagementServer) {
172
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
173
}
174
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
175
servers.push(this.extensionManagementServerService.remoteExtensionManagementServer);
176
}
177
if (this.extensionManagementServerService.webExtensionManagementServer) {
178
servers.push(this.extensionManagementServerService.webExtensionManagementServer);
179
}
180
const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => {
181
return servers.length > 1 ? `${server.label} - ${viewTitle}` : viewTitle;
182
};
183
let installedWebExtensionsContextChangeEvent = Event.None;
184
if (this.extensionManagementServerService.webExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
185
const interestingContextKeys = new Set();
186
interestingContextKeys.add('hasInstalledWebExtensions');
187
installedWebExtensionsContextChangeEvent = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(interestingContextKeys));
188
}
189
const serverLabelChangeEvent = Event.any(this.labelService.onDidChangeFormatters, installedWebExtensionsContextChangeEvent);
190
for (const server of servers) {
191
const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server);
192
const onDidChangeTitle = Event.map<void, string>(serverLabelChangeEvent, () => getInstalledViewName());
193
const id = servers.length > 1 ? `workbench.views.extensions.${server.id}.installed` : `workbench.views.extensions.installed`;
194
/* Installed extensions view */
195
viewDescriptors.push({
196
id,
197
get name() {
198
return {
199
value: getInstalledViewName(),
200
original: getViewName('Installed', server)
201
};
202
},
203
weight: 100,
204
order: 1,
205
when: ContextKeyExpr.and(DefaultViewsContext),
206
ctorDescriptor: new SyncDescriptor(ServerInstalledExtensionsView, [{ server, flexibleHeight: true, onDidChangeTitle }]),
207
/* Installed extensions views shall not be allowed to hidden when there are more than one server */
208
canToggleVisibility: servers.length === 1
209
});
210
211
if (server === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer) {
212
this._register(registerAction2(class InstallLocalExtensionsInRemoteAction2 extends Action2 {
213
constructor() {
214
super({
215
id: 'workbench.extensions.installLocalExtensions',
216
get title() {
217
return localize2('select and install local extensions', "Install Local Extensions in '{0}'...", server.label);
218
},
219
category: REMOTE_CATEGORY,
220
icon: installLocalInRemoteIcon,
221
f1: true,
222
menu: {
223
id: MenuId.ViewTitle,
224
when: ContextKeyExpr.equals('view', id),
225
group: 'navigation',
226
}
227
});
228
}
229
run(accessor: ServicesAccessor): Promise<void> {
230
return accessor.get(IInstantiationService).createInstance(InstallLocalExtensionsInRemoteAction).run();
231
}
232
}));
233
}
234
}
235
236
if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
237
this._register(registerAction2(class InstallRemoteExtensionsInLocalAction2 extends Action2 {
238
constructor() {
239
super({
240
id: 'workbench.extensions.actions.installLocalExtensionsInRemote',
241
title: localize2('install remote in local', 'Install Remote Extensions Locally...'),
242
category: REMOTE_CATEGORY,
243
f1: true
244
});
245
}
246
run(accessor: ServicesAccessor): Promise<void> {
247
return accessor.get(IInstantiationService).createInstance(InstallRemoteExtensionsInLocalAction, 'workbench.extensions.actions.installLocalExtensionsInRemote').run();
248
}
249
}));
250
}
251
252
/*
253
* Default popular extensions view
254
* Separate view for popular extensions required as we need to show popular and recommended sections
255
* in the default view when there is no search text, and user has no installed extensions.
256
*/
257
viewDescriptors.push({
258
id: 'workbench.views.extensions.popular',
259
name: localize2('popularExtensions', "Popular"),
260
ctorDescriptor: new SyncDescriptor(DefaultPopularExtensionsView, [{ hideBadge: true }]),
261
when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.not('hasInstalledExtensions'), CONTEXT_HAS_GALLERY),
262
weight: 60,
263
order: 2,
264
canToggleVisibility: false
265
});
266
267
/*
268
* Default recommended extensions view
269
* When user has installed extensions, this is shown along with the views for enabled & disabled extensions
270
* When user has no installed extensions, this is shown along with the view for popular extensions
271
*/
272
viewDescriptors.push({
273
id: 'extensions.recommendedList',
274
name: localize2('recommendedExtensions', "Recommended"),
275
ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView, [{ flexibleHeight: true }]),
276
when: ContextKeyExpr.and(DefaultViewsContext, SortByUpdateDateContext.negate(), ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand'), CONTEXT_HAS_GALLERY),
277
weight: 40,
278
order: 3,
279
canToggleVisibility: true
280
});
281
282
/* Installed views shall be default in multi server window */
283
if (servers.length === 1) {
284
/*
285
* Default enabled extensions view - Shows all user installed enabled extensions.
286
* Hidden by default
287
*/
288
viewDescriptors.push({
289
id: 'workbench.views.extensions.enabled',
290
name: localize2('enabledExtensions', "Enabled"),
291
ctorDescriptor: new SyncDescriptor(EnabledExtensionsView, [{}]),
292
when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.has('hasInstalledExtensions')),
293
hideByDefault: true,
294
weight: 40,
295
order: 4,
296
canToggleVisibility: true
297
});
298
299
/*
300
* Default disabled extensions view - Shows all disabled extensions.
301
* Hidden by default
302
*/
303
viewDescriptors.push({
304
id: 'workbench.views.extensions.disabled',
305
name: localize2('disabledExtensions', "Disabled"),
306
ctorDescriptor: new SyncDescriptor(DisabledExtensionsView, [{}]),
307
when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.has('hasInstalledExtensions')),
308
hideByDefault: true,
309
weight: 10,
310
order: 5,
311
canToggleVisibility: true
312
});
313
314
}
315
316
return viewDescriptors;
317
}
318
319
private createSearchExtensionsViewDescriptors(): IViewDescriptor[] {
320
const viewDescriptors: IViewDescriptor[] = [];
321
322
/*
323
* View used for searching Marketplace
324
*/
325
viewDescriptors.push({
326
id: 'workbench.views.extensions.marketplace',
327
name: localize2('marketPlace', "Marketplace"),
328
ctorDescriptor: new SyncDescriptor(SearchMarketplaceExtensionsView, [{}]),
329
when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions'), CONTEXT_HAS_GALLERY)
330
});
331
332
/*
333
* View used for searching all installed extensions
334
*/
335
viewDescriptors.push({
336
id: 'workbench.views.extensions.searchInstalled',
337
name: localize2('installed', "Installed"),
338
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
339
when: ContextKeyExpr.or(ContextKeyExpr.has('searchInstalledExtensions'), ContextKeyExpr.has('installedExtensions')),
340
});
341
342
/*
343
* View used for searching recently updated extensions
344
*/
345
viewDescriptors.push({
346
id: 'workbench.views.extensions.searchRecentlyUpdated',
347
name: localize2('recently updated', "Recently Updated"),
348
ctorDescriptor: new SyncDescriptor(RecentlyUpdatedExtensionsView, [{}]),
349
when: ContextKeyExpr.or(SearchExtensionUpdatesContext, ContextKeyExpr.has('searchRecentlyUpdatedExtensions')),
350
order: 2,
351
});
352
353
/*
354
* View used for searching enabled extensions
355
*/
356
viewDescriptors.push({
357
id: 'workbench.views.extensions.searchEnabled',
358
name: localize2('enabled', "Enabled"),
359
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
360
when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')),
361
});
362
363
/*
364
* View used for searching disabled extensions
365
*/
366
viewDescriptors.push({
367
id: 'workbench.views.extensions.searchDisabled',
368
name: localize2('disabled', "Disabled"),
369
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
370
when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')),
371
});
372
373
/*
374
* View used for searching outdated extensions
375
*/
376
viewDescriptors.push({
377
id: OUTDATED_EXTENSIONS_VIEW_ID,
378
name: localize2('availableUpdates', "Available Updates"),
379
ctorDescriptor: new SyncDescriptor(OutdatedExtensionsView, [{}]),
380
when: ContextKeyExpr.or(SearchExtensionUpdatesContext, ContextKeyExpr.has('searchOutdatedExtensions')),
381
order: 1,
382
});
383
384
/*
385
* View used for searching builtin extensions
386
*/
387
viewDescriptors.push({
388
id: 'workbench.views.extensions.searchBuiltin',
389
name: localize2('builtin', "Builtin"),
390
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
391
when: ContextKeyExpr.and(ContextKeyExpr.has('searchBuiltInExtensions')),
392
});
393
394
/*
395
* View used for searching workspace unsupported extensions
396
*/
397
viewDescriptors.push({
398
id: 'workbench.views.extensions.searchWorkspaceUnsupported',
399
name: localize2('workspaceUnsupported', "Workspace Unsupported"),
400
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
401
when: ContextKeyExpr.and(ContextKeyExpr.has('searchWorkspaceUnsupportedExtensions')),
402
});
403
404
return viewDescriptors;
405
}
406
407
private createRecommendedExtensionsViewDescriptors(): IViewDescriptor[] {
408
const viewDescriptors: IViewDescriptor[] = [];
409
410
viewDescriptors.push({
411
id: WORKSPACE_RECOMMENDATIONS_VIEW_ID,
412
name: localize2('workspaceRecommendedExtensions', "Workspace Recommendations"),
413
ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView, [{}]),
414
when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), WorkbenchStateContext.notEqualsTo('empty')),
415
order: 1
416
});
417
418
viewDescriptors.push({
419
id: 'workbench.views.extensions.otherRecommendations',
420
name: localize2('otherRecommendedExtensions', "Other Recommendations"),
421
ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView, [{}]),
422
when: ContextKeyExpr.has('recommendedExtensions'),
423
order: 2
424
});
425
426
return viewDescriptors;
427
}
428
429
private createBuiltinExtensionsViewDescriptors(): IViewDescriptor[] {
430
const viewDescriptors: IViewDescriptor[] = [];
431
432
const configuredCategories = ['themes', 'programming languages'];
433
const otherCategories = EXTENSION_CATEGORIES.filter(c => !configuredCategories.includes(c.toLowerCase()));
434
otherCategories.push(NONE_CATEGORY);
435
const otherCategoriesQuery = `${otherCategories.map(c => `category:"${c}"`).join(' ')} ${configuredCategories.map(c => `category:"-${c}"`).join(' ')}`;
436
viewDescriptors.push({
437
id: 'workbench.views.extensions.builtinFeatureExtensions',
438
name: localize2('builtinFeatureExtensions', "Features"),
439
ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin ${otherCategoriesQuery}` }]),
440
when: ContextKeyExpr.has('builtInExtensions'),
441
});
442
443
viewDescriptors.push({
444
id: 'workbench.views.extensions.builtinThemeExtensions',
445
name: localize2('builtInThemesExtensions', "Themes"),
446
ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin category:themes` }]),
447
when: ContextKeyExpr.has('builtInExtensions'),
448
});
449
450
viewDescriptors.push({
451
id: 'workbench.views.extensions.builtinProgrammingLanguageExtensions',
452
name: localize2('builtinProgrammingLanguageExtensions', "Programming Languages"),
453
ctorDescriptor: new SyncDescriptor(StaticQueryExtensionsView, [{ query: `@builtin category:"programming languages"` }]),
454
when: ContextKeyExpr.has('builtInExtensions'),
455
});
456
457
return viewDescriptors;
458
}
459
460
private createUnsupportedWorkspaceExtensionsViewDescriptors(): IViewDescriptor[] {
461
const viewDescriptors: IViewDescriptor[] = [];
462
463
viewDescriptors.push({
464
id: 'workbench.views.extensions.untrustedUnsupportedExtensions',
465
name: localize2('untrustedUnsupportedExtensions', "Disabled in Restricted Mode"),
466
ctorDescriptor: new SyncDescriptor(UntrustedWorkspaceUnsupportedExtensionsView, [{}]),
467
when: ContextKeyExpr.and(SearchUnsupportedWorkspaceExtensionsContext),
468
});
469
470
viewDescriptors.push({
471
id: 'workbench.views.extensions.untrustedPartiallySupportedExtensions',
472
name: localize2('untrustedPartiallySupportedExtensions', "Limited in Restricted Mode"),
473
ctorDescriptor: new SyncDescriptor(UntrustedWorkspacePartiallySupportedExtensionsView, [{}]),
474
when: ContextKeyExpr.and(SearchUnsupportedWorkspaceExtensionsContext),
475
});
476
477
viewDescriptors.push({
478
id: 'workbench.views.extensions.virtualUnsupportedExtensions',
479
name: localize2('virtualUnsupportedExtensions', "Disabled in Virtual Workspaces"),
480
ctorDescriptor: new SyncDescriptor(VirtualWorkspaceUnsupportedExtensionsView, [{}]),
481
when: ContextKeyExpr.and(VirtualWorkspaceContext, SearchUnsupportedWorkspaceExtensionsContext),
482
});
483
484
viewDescriptors.push({
485
id: 'workbench.views.extensions.virtualPartiallySupportedExtensions',
486
name: localize2('virtualPartiallySupportedExtensions', "Limited in Virtual Workspaces"),
487
ctorDescriptor: new SyncDescriptor(VirtualWorkspacePartiallySupportedExtensionsView, [{}]),
488
when: ContextKeyExpr.and(VirtualWorkspaceContext, SearchUnsupportedWorkspaceExtensionsContext),
489
});
490
491
return viewDescriptors;
492
}
493
494
private createOtherLocalFilteredExtensionsViewDescriptors(): IViewDescriptor[] {
495
const viewDescriptors: IViewDescriptor[] = [];
496
497
viewDescriptors.push({
498
id: 'workbench.views.extensions.deprecatedExtensions',
499
name: localize2('deprecated', "Deprecated"),
500
ctorDescriptor: new SyncDescriptor(DeprecatedExtensionsView, [{}]),
501
when: ContextKeyExpr.and(SearchDeprecatedExtensionsContext),
502
});
503
504
return viewDescriptors;
505
}
506
507
}
508
509
export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer {
510
511
private readonly extensionsSearchValueContextKey: IContextKey<string>;
512
private readonly defaultViewsContextKey: IContextKey<boolean>;
513
private readonly sortByContextKey: IContextKey<string>;
514
private readonly searchMarketplaceExtensionsContextKey: IContextKey<boolean>;
515
private readonly searchMcpServersContextKey: IContextKey<boolean>;
516
private readonly searchHasTextContextKey: IContextKey<boolean>;
517
private readonly sortByUpdateDateContextKey: IContextKey<boolean>;
518
private readonly installedExtensionsContextKey: IContextKey<boolean>;
519
private readonly searchInstalledExtensionsContextKey: IContextKey<boolean>;
520
private readonly searchRecentlyUpdatedExtensionsContextKey: IContextKey<boolean>;
521
private readonly searchExtensionUpdatesContextKey: IContextKey<boolean>;
522
private readonly searchOutdatedExtensionsContextKey: IContextKey<boolean>;
523
private readonly searchEnabledExtensionsContextKey: IContextKey<boolean>;
524
private readonly searchDisabledExtensionsContextKey: IContextKey<boolean>;
525
private readonly hasInstalledExtensionsContextKey: IContextKey<boolean>;
526
private readonly builtInExtensionsContextKey: IContextKey<boolean>;
527
private readonly searchBuiltInExtensionsContextKey: IContextKey<boolean>;
528
private readonly searchWorkspaceUnsupportedExtensionsContextKey: IContextKey<boolean>;
529
private readonly searchDeprecatedExtensionsContextKey: IContextKey<boolean>;
530
private readonly recommendedExtensionsContextKey: IContextKey<boolean>;
531
532
private searchDelayer: Delayer<void>;
533
private root: HTMLElement | undefined;
534
private header: HTMLElement | undefined;
535
private searchBox: SuggestEnabledInput | undefined;
536
private notificationContainer: HTMLElement | undefined;
537
private readonly searchViewletState: MementoObject;
538
private extensionGalleryManifest: IExtensionGalleryManifest | null = null;
539
540
constructor(
541
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
542
@ITelemetryService telemetryService: ITelemetryService,
543
@IProgressService private readonly progressService: IProgressService,
544
@IInstantiationService instantiationService: IInstantiationService,
545
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
546
@IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,
547
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
548
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
549
@INotificationService private readonly notificationService: INotificationService,
550
@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,
551
@IThemeService themeService: IThemeService,
552
@IConfigurationService configurationService: IConfigurationService,
553
@IStorageService storageService: IStorageService,
554
@IWorkspaceContextService contextService: IWorkspaceContextService,
555
@IContextKeyService private readonly contextKeyService: IContextKeyService,
556
@IContextMenuService contextMenuService: IContextMenuService,
557
@IExtensionService extensionService: IExtensionService,
558
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
559
@IPreferencesService private readonly preferencesService: IPreferencesService,
560
@ICommandService private readonly commandService: ICommandService,
561
@ILogService logService: ILogService,
562
) {
563
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, logService);
564
565
this.searchDelayer = new Delayer(500);
566
this.extensionsSearchValueContextKey = ExtensionsSearchValueContext.bindTo(contextKeyService);
567
this.defaultViewsContextKey = DefaultViewsContext.bindTo(contextKeyService);
568
this.sortByContextKey = ExtensionsSortByContext.bindTo(contextKeyService);
569
this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService);
570
this.searchMcpServersContextKey = SearchMcpServersContext.bindTo(contextKeyService);
571
this.searchHasTextContextKey = SearchHasTextContext.bindTo(contextKeyService);
572
this.sortByUpdateDateContextKey = SortByUpdateDateContext.bindTo(contextKeyService);
573
this.installedExtensionsContextKey = InstalledExtensionsContext.bindTo(contextKeyService);
574
this.searchInstalledExtensionsContextKey = SearchInstalledExtensionsContext.bindTo(contextKeyService);
575
this.searchRecentlyUpdatedExtensionsContextKey = SearchRecentlyUpdatedExtensionsContext.bindTo(contextKeyService);
576
this.searchExtensionUpdatesContextKey = SearchExtensionUpdatesContext.bindTo(contextKeyService);
577
this.searchWorkspaceUnsupportedExtensionsContextKey = SearchUnsupportedWorkspaceExtensionsContext.bindTo(contextKeyService);
578
this.searchDeprecatedExtensionsContextKey = SearchDeprecatedExtensionsContext.bindTo(contextKeyService);
579
this.searchOutdatedExtensionsContextKey = SearchOutdatedExtensionsContext.bindTo(contextKeyService);
580
this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService);
581
this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService);
582
this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService);
583
this.builtInExtensionsContextKey = BuiltInExtensionsContext.bindTo(contextKeyService);
584
this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService);
585
this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService);
586
this._register(this.paneCompositeService.onDidPaneCompositeOpen(e => { if (e.viewContainerLocation === ViewContainerLocation.Sidebar) { this.onViewletOpen(e.composite); } }, this));
587
this._register(extensionsWorkbenchService.onReset(() => this.refresh()));
588
this.searchViewletState = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE);
589
590
extensionGalleryManifestService.getExtensionGalleryManifest()
591
.then(galleryManifest => {
592
this.extensionGalleryManifest = galleryManifest;
593
this._register(extensionGalleryManifestService.onDidChangeExtensionGalleryManifest(galleryManifest => {
594
this.extensionGalleryManifest = galleryManifest;
595
this.refresh();
596
}));
597
});
598
}
599
600
get searchValue(): string | undefined {
601
return this.searchBox?.getValue();
602
}
603
604
override create(parent: HTMLElement): void {
605
parent.classList.add('extensions-viewlet');
606
this.root = parent;
607
608
const overlay = append(this.root, $('.overlay'));
609
const overlayBackgroundColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
610
overlay.style.backgroundColor = overlayBackgroundColor;
611
hide(overlay);
612
613
this.header = append(this.root, $('.header'));
614
const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
615
616
const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';
617
618
const searchContainer = append(this.header, $('.extensions-search-container'));
619
620
this.searchBox = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, searchContainer, {
621
triggerCharacters: ['@'],
622
sortKey: (item: string) => {
623
if (item.indexOf(':') === -1) { return 'a'; }
624
else if (/ext:/.test(item) || /id:/.test(item) || /tag:/.test(item)) { return 'b'; }
625
else if (/sort:/.test(item)) { return 'c'; }
626
else { return 'd'; }
627
},
628
provideResults: (query: string) => Query.suggestions(query, this.extensionGalleryManifest)
629
}, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue }));
630
631
this.notificationContainer = append(this.header, $('.notification-container.hidden', { 'tabindex': '0' }));
632
this.renderNotificaiton();
633
this._register(this.extensionsWorkbenchService.onDidChangeExtensionsNotification(() => this.renderNotificaiton()));
634
635
this.updateInstalledExtensionsContexts();
636
if (this.searchBox.getValue()) {
637
this.triggerSearch();
638
}
639
640
this._register(this.searchBox.onInputDidChange(() => {
641
this.sortByContextKey.set(Query.parse(this.searchBox?.getValue() ?? '').sortBy);
642
this.triggerSearch();
643
}, this));
644
645
this._register(this.searchBox.onShouldFocusResults(() => this.focusListView(), this));
646
647
const controlElement = append(searchContainer, $('.extensions-search-actions-container'));
648
this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, controlElement, extensionsSearchActionsMenu, {
649
toolbarOptions: {
650
primaryGroup: () => true,
651
},
652
actionViewItemProvider: (action, options) => createActionViewItem(this.instantiationService, action, options)
653
}));
654
655
// Register DragAndDrop support
656
this._register(new DragAndDropObserver(this.root, {
657
onDragEnter: (e: DragEvent) => {
658
if (this.isSupportedDragElement(e)) {
659
show(overlay);
660
}
661
},
662
onDragLeave: (e: DragEvent) => {
663
if (this.isSupportedDragElement(e)) {
664
hide(overlay);
665
}
666
},
667
onDragOver: (e: DragEvent) => {
668
if (this.isSupportedDragElement(e)) {
669
e.dataTransfer!.dropEffect = 'copy';
670
}
671
},
672
onDrop: async (e: DragEvent) => {
673
if (this.isSupportedDragElement(e)) {
674
hide(overlay);
675
676
const vsixs = coalesce((await this.instantiationService.invokeFunction(accessor => extractEditorsAndFilesDropData(accessor, e)))
677
.map(editor => editor.resource && extname(editor.resource) === '.vsix' ? editor.resource : undefined));
678
679
if (vsixs.length > 0) {
680
try {
681
// Attempt to install the extension(s)
682
await this.commandService.executeCommand(INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, vsixs);
683
}
684
catch (err) {
685
this.notificationService.error(err);
686
}
687
}
688
}
689
}
690
}));
691
692
super.create(append(this.root, $('.extensions')));
693
694
const focusTracker = this._register(trackFocus(this.root));
695
const isSearchBoxFocused = () => this.searchBox?.inputWidget.hasWidgetFocus();
696
this._register(registerNavigableContainer({
697
name: 'extensionsView',
698
focusNotifiers: [focusTracker],
699
focusNextWidget: () => {
700
if (isSearchBoxFocused()) {
701
this.focusListView();
702
}
703
},
704
focusPreviousWidget: () => {
705
if (!isSearchBoxFocused()) {
706
this.searchBox?.focus();
707
}
708
}
709
}));
710
}
711
712
override focus(): void {
713
super.focus();
714
this.searchBox?.focus();
715
}
716
717
private _dimension: Dimension | undefined;
718
override layout(dimension: Dimension): void {
719
this._dimension = dimension;
720
if (this.root) {
721
this.root.classList.toggle('narrow', dimension.width <= 250);
722
this.root.classList.toggle('mini', dimension.width <= 200);
723
}
724
this.searchBox?.layout(new Dimension(dimension.width - 34 - /*padding*/8 - (24 * 2), 20));
725
const searchBoxHeight = 20 + 21 /*margin*/;
726
const headerHeight = this.header && !!this.notificationContainer?.childNodes.length ? this.notificationContainer.clientHeight + searchBoxHeight + 10 /*margin*/ : searchBoxHeight;
727
this.header!.style.height = `${headerHeight}px`;
728
super.layout(new Dimension(dimension.width, dimension.height - headerHeight));
729
}
730
731
override getOptimalWidth(): number {
732
return 400;
733
}
734
735
search(value: string): void {
736
if (this.searchBox && this.searchBox.getValue() !== value) {
737
this.searchBox.setValue(value);
738
}
739
}
740
741
async refresh(): Promise<void> {
742
await this.updateInstalledExtensionsContexts();
743
this.doSearch(true);
744
if (this.configurationService.getValue(AutoCheckUpdatesConfigurationKey)) {
745
this.extensionsWorkbenchService.checkForUpdates();
746
}
747
}
748
749
private readonly notificationDisposables = this._register(new MutableDisposable<DisposableStore>());
750
private renderNotificaiton(): void {
751
if (!this.notificationContainer) {
752
return;
753
}
754
755
clearNode(this.notificationContainer);
756
this.notificationDisposables.value = new DisposableStore();
757
const status = this.extensionsWorkbenchService.getExtensionsNotification();
758
const query = status?.extensions.map(extension => `@id:${extension.identifier.id}`).join(' ');
759
if (status && (query === this.searchBox?.getValue() || !this.searchMarketplaceExtensionsContextKey.get())) {
760
this.notificationContainer.setAttribute('aria-label', status.message);
761
this.notificationContainer.classList.remove('hidden');
762
const messageContainer = append(this.notificationContainer, $('.message-container'));
763
append(messageContainer, $('span')).className = SeverityIcon.className(status.severity);
764
append(messageContainer, $('span.message', undefined, status.message));
765
const showAction = append(messageContainer,
766
$('span.message-text-action', {
767
'tabindex': '0',
768
'role': 'button',
769
'aria-label': `${status.message}. ${localize('click show', "Click to Show")}`
770
}, localize('show', "Show")));
771
this.notificationDisposables.value.add(addDisposableListener(showAction, EventType.CLICK, () => this.search(query ?? '')));
772
this.notificationDisposables.value.add(addDisposableListener(showAction, EventType.KEY_DOWN, (e: KeyboardEvent) => {
773
const standardKeyboardEvent = new StandardKeyboardEvent(e);
774
if (standardKeyboardEvent.keyCode === KeyCode.Enter || standardKeyboardEvent.keyCode === KeyCode.Space) {
775
this.search(query ?? '');
776
}
777
standardKeyboardEvent.stopPropagation();
778
}));
779
const dismissAction = append(this.notificationContainer,
780
$(`span.message-action${ThemeIcon.asCSSSelector(Codicon.close)}`, {
781
'tabindex': '0',
782
'role': 'button',
783
'aria-label': localize('dismiss', "Dismiss"),
784
'title': localize('dismiss', "Dismiss")
785
}));
786
this.notificationDisposables.value.add(addDisposableListener(dismissAction, EventType.CLICK, () => status.dismiss()));
787
this.notificationDisposables.value.add(addDisposableListener(dismissAction, EventType.KEY_DOWN, (e: KeyboardEvent) => {
788
const standardKeyboardEvent = new StandardKeyboardEvent(e);
789
if (standardKeyboardEvent.keyCode === KeyCode.Enter || standardKeyboardEvent.keyCode === KeyCode.Space) {
790
status.dismiss();
791
}
792
standardKeyboardEvent.stopPropagation();
793
}));
794
} else {
795
this.notificationContainer.removeAttribute('aria-label');
796
this.notificationContainer.classList.add('hidden');
797
}
798
799
if (this._dimension) {
800
this.layout(this._dimension);
801
}
802
}
803
804
private async updateInstalledExtensionsContexts(): Promise<void> {
805
const result = await this.extensionsWorkbenchService.queryLocal();
806
this.hasInstalledExtensionsContextKey.set(result.some(r => !r.isBuiltin));
807
}
808
809
private triggerSearch(): void {
810
this.searchDelayer.trigger(() => this.doSearch(), this.searchBox && this.searchBox.getValue() ? 500 : 0).then(undefined, err => this.onError(err));
811
}
812
813
private normalizedQuery(): string {
814
return this.searchBox
815
? this.searchBox.getValue()
816
.trim()
817
.replace(/@category/g, 'category')
818
.replace(/@tag:/g, 'tag:')
819
.replace(/@ext:/g, 'ext:')
820
.replace(/@featured/g, 'featured')
821
.replace(/@popular/g, this.extensionManagementServerService.webExtensionManagementServer && !this.extensionManagementServerService.localExtensionManagementServer && !this.extensionManagementServerService.remoteExtensionManagementServer ? '@web' : '@popular')
822
: '';
823
}
824
825
protected override saveState(): void {
826
const value = this.searchBox ? this.searchBox.getValue() : '';
827
if (ExtensionsListView.isLocalExtensionsQuery(value)) {
828
this.searchViewletState['query.value'] = value;
829
} else {
830
this.searchViewletState['query.value'] = '';
831
}
832
super.saveState();
833
}
834
835
private doSearch(refresh?: boolean): Promise<void> {
836
const value = this.normalizedQuery();
837
this.contextKeyService.bufferChangeEvents(() => {
838
const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value);
839
this.searchHasTextContextKey.set(value.trim() !== '');
840
this.extensionsSearchValueContextKey.set(value);
841
this.installedExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value));
842
this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isSearchInstalledExtensionsQuery(value));
843
this.searchRecentlyUpdatedExtensionsContextKey.set(ExtensionsListView.isSearchRecentlyUpdatedQuery(value) && !ExtensionsListView.isSearchExtensionUpdatesQuery(value));
844
this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value) && !ExtensionsListView.isSearchExtensionUpdatesQuery(value));
845
this.searchExtensionUpdatesContextKey.set(ExtensionsListView.isSearchExtensionUpdatesQuery(value));
846
this.searchEnabledExtensionsContextKey.set(ExtensionsListView.isEnabledExtensionsQuery(value));
847
this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value));
848
this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isSearchBuiltInExtensionsQuery(value));
849
this.searchWorkspaceUnsupportedExtensionsContextKey.set(ExtensionsListView.isSearchWorkspaceUnsupportedExtensionsQuery(value));
850
this.searchDeprecatedExtensionsContextKey.set(ExtensionsListView.isSearchDeprecatedExtensionsQuery(value));
851
this.builtInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));
852
this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery);
853
this.searchMcpServersContextKey.set(!!value && /@mcp\s?.*/i.test(value));
854
this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery && !this.searchMcpServersContextKey.get());
855
this.sortByUpdateDateContextKey.set(ExtensionsListView.isSortUpdateDateQuery(value));
856
this.defaultViewsContextKey.set(!value || ExtensionsListView.isSortInstalledExtensionsQuery(value));
857
});
858
859
this.renderNotificaiton();
860
861
return this.showExtensionsViews(this.panes);
862
}
863
864
protected override onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] {
865
const addedViews = super.onDidAddViewDescriptors(added);
866
this.showExtensionsViews(addedViews);
867
return addedViews;
868
}
869
870
private async showExtensionsViews(views: ViewPane[]): Promise<void> {
871
await this.progress(Promise.all(views.map(async view => {
872
if (view instanceof AbstractExtensionsListView) {
873
const model = await view.show(this.normalizedQuery());
874
this.alertSearchResult(model.length, view.id);
875
}
876
})));
877
}
878
879
private alertSearchResult(count: number, viewId: string): void {
880
const view = this.viewContainerModel.visibleViewDescriptors.find(view => view.id === viewId);
881
switch (count) {
882
case 0:
883
break;
884
case 1:
885
if (view) {
886
alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", view.name.value));
887
} else {
888
alert(localize('extensionFound', "1 extension found."));
889
}
890
break;
891
default:
892
if (view) {
893
alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, view.name.value));
894
} else {
895
alert(localize('extensionsFound', "{0} extensions found.", count));
896
}
897
break;
898
}
899
}
900
901
private getFirstExpandedPane(): ExtensionsListView | undefined {
902
for (const pane of this.panes) {
903
if (pane.isExpanded() && pane instanceof ExtensionsListView) {
904
return pane;
905
}
906
}
907
return undefined;
908
}
909
910
private focusListView(): void {
911
const pane = this.getFirstExpandedPane();
912
if (pane && pane.count() > 0) {
913
pane.focus();
914
}
915
}
916
917
private onViewletOpen(viewlet: IPaneComposite): void {
918
if (!viewlet || viewlet.getId() === VIEWLET_ID) {
919
return;
920
}
921
922
if (this.configurationService.getValue<boolean>(CloseExtensionDetailsOnViewChangeKey)) {
923
const promises = this.editorGroupService.groups.map(group => {
924
const editors = group.editors.filter(input => input instanceof ExtensionsInput);
925
926
return group.closeEditors(editors);
927
});
928
929
Promise.all(promises);
930
}
931
}
932
933
private progress<T>(promise: Promise<T>): Promise<T> {
934
return this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => promise);
935
}
936
937
private onError(err: Error): void {
938
if (isCancellationError(err)) {
939
return;
940
}
941
942
const message = err && err.message || '';
943
944
if (/ECONNREFUSED/.test(message)) {
945
const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), [
946
new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())
947
]);
948
949
this.notificationService.error(error);
950
return;
951
}
952
953
this.notificationService.error(err);
954
}
955
956
private isSupportedDragElement(e: DragEvent): boolean {
957
if (e.dataTransfer) {
958
const typesLowerCase = e.dataTransfer.types.map(t => t.toLocaleLowerCase());
959
return typesLowerCase.indexOf('files') !== -1;
960
}
961
962
return false;
963
}
964
}
965
966
export class StatusUpdater extends Disposable implements IWorkbenchContribution {
967
968
private readonly badgeHandle = this._register(new MutableDisposable());
969
970
constructor(
971
@IActivityService private readonly activityService: IActivityService,
972
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
973
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
974
@IConfigurationService private readonly configurationService: IConfigurationService
975
) {
976
super();
977
this.onServiceChange();
978
this._register(Event.any(Event.debounce(extensionsWorkbenchService.onChange, () => undefined, 100, undefined, undefined, undefined, this._store), extensionsWorkbenchService.onDidChangeExtensionsNotification)(this.onServiceChange, this));
979
}
980
981
private onServiceChange(): void {
982
this.badgeHandle.clear();
983
let badge: IBadge | undefined;
984
985
const extensionsNotification = this.extensionsWorkbenchService.getExtensionsNotification();
986
if (extensionsNotification) {
987
if (extensionsNotification.severity === Severity.Warning) {
988
badge = new WarningBadge(() => extensionsNotification.message);
989
}
990
}
991
992
else {
993
const actionRequired = this.configurationService.getValue(AutoRestartConfigurationKey) === true ? [] : this.extensionsWorkbenchService.installed.filter(e => e.runtimeState !== undefined);
994
const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) && !actionRequired.includes(e) ? 1 : 0), 0);
995
const newBadgeNumber = outdated + actionRequired.length;
996
if (newBadgeNumber > 0) {
997
let msg = '';
998
if (outdated) {
999
msg += outdated === 1 ? localize('extensionToUpdate', '{0} requires update', outdated) : localize('extensionsToUpdate', '{0} require update', outdated);
1000
}
1001
if (outdated > 0 && actionRequired.length > 0) {
1002
msg += ', ';
1003
}
1004
if (actionRequired.length) {
1005
msg += actionRequired.length === 1 ? localize('extensionToReload', '{0} requires restart', actionRequired.length) : localize('extensionsToReload', '{0} require restart', actionRequired.length);
1006
}
1007
badge = new NumberBadge(newBadgeNumber, () => msg);
1008
}
1009
}
1010
1011
if (badge) {
1012
this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge });
1013
}
1014
}
1015
}
1016
1017
export class MaliciousExtensionChecker implements IWorkbenchContribution {
1018
1019
constructor(
1020
@IExtensionManagementService private readonly extensionsManagementService: IExtensionManagementService,
1021
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
1022
@IHostService private readonly hostService: IHostService,
1023
@ILogService private readonly logService: ILogService,
1024
@INotificationService private readonly notificationService: INotificationService,
1025
@ICommandService private readonly commandService: ICommandService,
1026
) {
1027
this.loopCheckForMaliciousExtensions();
1028
}
1029
1030
private loopCheckForMaliciousExtensions(): void {
1031
this.checkForMaliciousExtensions()
1032
.then(() => timeout(1000 * 60 * 5)) // every five minutes
1033
.then(() => this.loopCheckForMaliciousExtensions());
1034
}
1035
1036
private async checkForMaliciousExtensions(): Promise<void> {
1037
try {
1038
const maliciousExtensions: [ILocalExtension, string | undefined][] = [];
1039
let shouldRestartExtensions = false;
1040
let shouldReloadWindow = false;
1041
for (const extension of this.extensionsWorkbenchService.installed) {
1042
if (extension.isMalicious && extension.local) {
1043
maliciousExtensions.push([extension.local, extension.maliciousInfoLink]);
1044
shouldRestartExtensions = shouldRestartExtensions || extension.runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions;
1045
shouldReloadWindow = shouldReloadWindow || extension.runtimeState?.action === ExtensionRuntimeActionType.ReloadWindow;
1046
}
1047
}
1048
if (maliciousExtensions.length) {
1049
await this.extensionsManagementService.uninstallExtensions(maliciousExtensions.map(e => ({ extension: e[0], options: { remove: true } })));
1050
for (const [extension, link] of maliciousExtensions) {
1051
const buttons: IPromptChoice[] = [];
1052
if (shouldRestartExtensions || shouldReloadWindow) {
1053
buttons.push({
1054
label: shouldRestartExtensions ? localize('restartNow', "Restart Extensions") : localize('reloadNow', "Reload Now"),
1055
run: () => shouldRestartExtensions ? this.extensionsWorkbenchService.updateRunningExtensions() : this.hostService.reload()
1056
});
1057
}
1058
if (link) {
1059
buttons.push({
1060
label: localize('learnMore', "Learn More"),
1061
run: () => this.commandService.executeCommand('vscode.open', URI.parse(link))
1062
});
1063
}
1064
this.notificationService.prompt(
1065
Severity.Warning,
1066
localize('malicious warning', "The extension '{0}' was found to be problematic and has been uninstalled", extension.manifest.displayName || extension.identifier.id),
1067
buttons,
1068
{
1069
sticky: true,
1070
priority: NotificationPriority.URGENT
1071
}
1072
);
1073
}
1074
}
1075
1076
} catch (err) {
1077
this.logService.error(err);
1078
}
1079
}
1080
}
1081
1082
export class ExtensionMarketplaceStatusUpdater extends Disposable implements IWorkbenchContribution {
1083
1084
private readonly badgeHandle = this._register(new MutableDisposable());
1085
private readonly accountBadgeDisposable = this._register(new MutableDisposable());
1086
1087
constructor(
1088
@IActivityService private readonly activityService: IActivityService,
1089
@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService
1090
) {
1091
super();
1092
this.updateBadge();
1093
this._register(this.extensionGalleryManifestService.onDidChangeExtensionGalleryManifestStatus(() => this.updateBadge()));
1094
}
1095
1096
private async updateBadge(): Promise<void> {
1097
this.badgeHandle.clear();
1098
1099
const status = this.extensionGalleryManifestService.extensionGalleryManifestStatus;
1100
let badge: IBadge | undefined;
1101
1102
switch (status) {
1103
case ExtensionGalleryManifestStatus.RequiresSignIn:
1104
badge = new NumberBadge(1, () => localize('signInRequired', "Sign in required to access marketplace"));
1105
break;
1106
case ExtensionGalleryManifestStatus.AccessDenied:
1107
badge = new WarningBadge(() => localize('accessDenied', "Access denied to marketplace"));
1108
break;
1109
}
1110
1111
if (badge) {
1112
this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge });
1113
}
1114
1115
this.accountBadgeDisposable.clear();
1116
if (status === ExtensionGalleryManifestStatus.RequiresSignIn) {
1117
const badge = new NumberBadge(1, () => localize('sign in enterprise marketplace', "Sign in to access Marketplace"));
1118
this.accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge });
1119
}
1120
}
1121
}
1122
1123