Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
5260 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/extensionActions.css';
7
import { localize, localize2 } from '../../../../nls.js';
8
import { IAction, Action, Separator, SubmenuAction, IActionChangeEvent } from '../../../../base/common/actions.js';
9
import { Delayer, Promises, Throttler } from '../../../../base/common/async.js';
10
import * as DOM from '../../../../base/browser/dom.js';
11
import { Emitter, Event } from '../../../../base/common/event.js';
12
import * as json from '../../../../base/common/json.js';
13
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
14
import { disposeIfDisposable } from '../../../../base/common/lifecycle.js';
15
import { IExtension, ExtensionState, IExtensionsWorkbenchService, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, ExtensionEditorTab, ExtensionRuntimeActionType, IExtensionArg, AutoUpdateConfigurationKey } from '../common/extensions.js';
16
import { ExtensionsConfigurationInitialContent } from '../common/extensionsFileTemplate.js';
17
import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, ExtensionManagementErrorCode, IAllowedExtensionsService, shouldRequireRepositorySignatureFor } from '../../../../platform/extensionManagement/common/extensionManagement.js';
18
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js';
19
import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js';
20
import { areSameExtensions, getExtensionId } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
21
import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, getWorkspaceSupportTypeMessage, TargetPlatform, isApplicationScopedExtension } from '../../../../platform/extensions/common/extensions.js';
22
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
23
import { IFileService, IFileContent } from '../../../../platform/files/common/files.js';
24
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js';
25
import { IHostService } from '../../../services/host/browser/host.js';
26
import { IExtensionService, toExtension, toExtensionDescription } from '../../../services/extensions/common/extensions.js';
27
import { URI } from '../../../../base/common/uri.js';
28
import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js';
29
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
30
import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from '../../../../platform/theme/common/themeService.js';
31
import { ThemeIcon } from '../../../../base/common/themables.js';
32
import { buttonBackground, buttonForeground, buttonHoverBackground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, registerColor, editorWarningForeground, editorInfoForeground, editorErrorForeground, buttonSeparator, buttonBorder, contrastBorder } from '../../../../platform/theme/common/colorRegistry.js';
33
import { IJSONEditingService } from '../../../services/configuration/common/jsonEditing.js';
34
import { ITextEditorSelection } from '../../../../platform/editor/common/editor.js';
35
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
36
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
37
import { MenuId, IMenuService, MenuItemAction, SubmenuItemAction } from '../../../../platform/actions/common/actions.js';
38
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from '../../../browser/actions/workspaceCommands.js';
39
import { INotificationService, IPromptChoice, Severity } from '../../../../platform/notification/common/notification.js';
40
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
41
import { IEditorService } from '../../../services/editor/common/editorService.js';
42
import { IQuickPickItem, IQuickInputService, QuickPickItem } from '../../../../platform/quickinput/common/quickInput.js';
43
import { CancellationToken } from '../../../../base/common/cancellation.js';
44
import { alert } from '../../../../base/browser/ui/aria/aria.js';
45
import { IWorkbenchThemeService, IWorkbenchTheme, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from '../../../services/themes/common/workbenchThemeService.js';
46
import { ILabelService } from '../../../../platform/label/common/label.js';
47
import { ITextFileService } from '../../../services/textfile/common/textfiles.js';
48
import { IProductService } from '../../../../platform/product/common/productService.js';
49
import { IDialogService, IPromptButton } from '../../../../platform/dialogs/common/dialogs.js';
50
import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';
51
import { IActionViewItemOptions, ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
52
import { EXTENSIONS_CONFIG, IExtensionsConfigContent } from '../../../services/extensionRecommendations/common/workspaceExtensionsConfig.js';
53
import { getErrorMessage, isCancellationError } from '../../../../base/common/errors.js';
54
import { IUserDataSyncEnablementService } from '../../../../platform/userDataSync/common/userDataSync.js';
55
import { IContextMenuProvider } from '../../../../base/browser/contextmenu.js';
56
import { ILogService } from '../../../../platform/log/common/log.js';
57
import { errorIcon, infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from './extensionsIcons.js';
58
import { isIOS, isWeb, language } from '../../../../base/common/platform.js';
59
import { IExtensionManifestPropertiesService } from '../../../services/extensions/common/extensionManifestPropertiesService.js';
60
import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js';
61
import { isVirtualWorkspace } from '../../../../platform/workspace/common/virtualWorkspace.js';
62
import { createCommandUri, escapeMarkdownSyntaxTokens, IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';
63
import { fromNow } from '../../../../base/common/date.js';
64
import { IPreferencesService } from '../../../services/preferences/common/preferences.js';
65
import { getLocale } from '../../../../platform/languagePacks/common/languagePacks.js';
66
import { ILocaleService } from '../../../services/localization/common/locale.js';
67
import { isString } from '../../../../base/common/types.js';
68
import { showWindowLogActionId } from '../../../services/log/common/logConstants.js';
69
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
70
import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from '../../../services/extensionManagement/common/extensionFeatures.js';
71
import { Registry } from '../../../../platform/registry/common/platform.js';
72
import { IUpdateService } from '../../../../platform/update/common/update.js';
73
import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js';
74
import { IAuthenticationUsageService } from '../../../services/authentication/browser/authenticationUsageService.js';
75
import { IExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';
76
import { IWorkbenchIssueService } from '../../issue/common/issue.js';
77
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
78
79
export class PromptExtensionInstallFailureAction extends Action {
80
81
constructor(
82
private readonly extension: IExtension,
83
private readonly options: InstallOptions | undefined,
84
private readonly version: string,
85
private readonly installOperation: InstallOperation,
86
private readonly error: Error,
87
@IProductService private readonly productService: IProductService,
88
@IOpenerService private readonly openerService: IOpenerService,
89
@INotificationService private readonly notificationService: INotificationService,
90
@IDialogService private readonly dialogService: IDialogService,
91
@ICommandService private readonly commandService: ICommandService,
92
@ILogService private readonly logService: ILogService,
93
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
94
@IInstantiationService private readonly instantiationService: IInstantiationService,
95
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
96
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
97
@IWorkbenchIssueService private readonly workbenchIssueService: IWorkbenchIssueService,
98
) {
99
super('extension.promptExtensionInstallFailure');
100
}
101
102
override async run(): Promise<void> {
103
if (isCancellationError(this.error)) {
104
return;
105
}
106
107
this.logService.error(this.error);
108
109
if (this.error.name === ExtensionManagementErrorCode.Unsupported) {
110
const productName = isWeb ? localize('VS Code for Web', "{0} for the Web", this.productService.nameLong) : this.productService.nameLong;
111
const message = localize('cannot be installed', "The '{0}' extension is not available in {1}. Click 'More Information' to learn more.", this.extension.displayName || this.extension.identifier.id, productName);
112
const { confirmed } = await this.dialogService.confirm({
113
type: Severity.Info,
114
message,
115
primaryButton: localize({ key: 'more information', comment: ['&& denotes a mnemonic'] }, "&&More Information"),
116
cancelButton: localize('close', "Close")
117
});
118
if (confirmed) {
119
this.openerService.open(isWeb ? URI.parse('https://aka.ms/vscode-web-extensions-guide') : URI.parse('https://aka.ms/vscode-remote'));
120
}
121
return;
122
}
123
124
if (ExtensionManagementErrorCode.ReleaseVersionNotFound === (<ExtensionManagementErrorCode>this.error.name)) {
125
await this.dialogService.prompt({
126
type: 'error',
127
message: getErrorMessage(this.error),
128
buttons: [{
129
label: localize('install prerelease', "Install Pre-Release"),
130
run: () => {
131
const installAction = this.instantiationService.createInstance(InstallAction, { installPreReleaseVersion: true });
132
installAction.extension = this.extension;
133
return installAction.run();
134
}
135
}],
136
cancelButton: localize('cancel', "Cancel")
137
});
138
return;
139
}
140
141
if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleApi, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.Deprecated].includes(<ExtensionManagementErrorCode>this.error.name)) {
142
await this.dialogService.info(getErrorMessage(this.error));
143
return;
144
}
145
146
if (ExtensionManagementErrorCode.PackageNotSigned === (<ExtensionManagementErrorCode>this.error.name)) {
147
await this.dialogService.prompt({
148
type: 'error',
149
message: localize('not signed', "'{0}' is an extension from an unknown source. Are you sure you want to install?", this.extension.displayName),
150
detail: getErrorMessage(this.error),
151
buttons: [{
152
label: localize('install anyway', "Install Anyway"),
153
run: () => {
154
const installAction = this.instantiationService.createInstance(InstallAction, { ...this.options, donotVerifySignature: true, });
155
installAction.extension = this.extension;
156
return installAction.run();
157
}
158
}],
159
cancelButton: true
160
});
161
return;
162
}
163
164
if (ExtensionManagementErrorCode.SignatureVerificationFailed === (<ExtensionManagementErrorCode>this.error.name)) {
165
await this.dialogService.prompt({
166
type: 'error',
167
message: localize('verification failed', "Cannot install '{0}' extension because {1} cannot verify the extension signature", this.extension.displayName, this.productService.nameLong),
168
detail: getErrorMessage(this.error),
169
buttons: [{
170
label: localize('learn more', "Learn More"),
171
run: () => this.openerService.open('https://code.visualstudio.com/docs/editor/extension-marketplace#_the-extension-signature-cannot-be-verified-by-vs-code')
172
}, {
173
label: localize('install donot verify', "Install Anyway (Don't Verify Signature)"),
174
run: () => {
175
const installAction = this.instantiationService.createInstance(InstallAction, { ...this.options, donotVerifySignature: true, });
176
installAction.extension = this.extension;
177
return installAction.run();
178
}
179
}],
180
cancelButton: true
181
});
182
return;
183
}
184
185
if (ExtensionManagementErrorCode.SignatureVerificationInternal === (<ExtensionManagementErrorCode>this.error.name)) {
186
await this.dialogService.prompt({
187
type: 'error',
188
message: localize('verification failed', "Cannot install '{0}' extension because {1} cannot verify the extension signature", this.extension.displayName, this.productService.nameLong),
189
detail: getErrorMessage(this.error),
190
buttons: [{
191
label: localize('learn more', "Learn More"),
192
run: () => this.openerService.open('https://code.visualstudio.com/docs/editor/extension-marketplace#_the-extension-signature-cannot-be-verified-by-vs-code')
193
}, {
194
label: localize('report issue', "Report Issue"),
195
run: () => this.workbenchIssueService.openReporter({
196
issueTitle: localize('report issue title', "Extension Signature Verification Failed: {0}", this.extension.displayName),
197
issueBody: localize('report issue body', "Please include following log `F1 > Open View... > Shared` below.\n\n")
198
})
199
}, {
200
label: localize('install donot verify', "Install Anyway (Don't Verify Signature)"),
201
run: () => {
202
const installAction = this.instantiationService.createInstance(InstallAction, { ...this.options, donotVerifySignature: true, });
203
installAction.extension = this.extension;
204
return installAction.run();
205
}
206
}],
207
cancelButton: true
208
});
209
return;
210
}
211
212
const operationMessage = this.installOperation === InstallOperation.Update ? localize('update operation', "Error while updating '{0}' extension.", this.extension.displayName || this.extension.identifier.id)
213
: localize('install operation', "Error while installing '{0}' extension.", this.extension.displayName || this.extension.identifier.id);
214
let additionalMessage;
215
const promptChoices: IPromptChoice[] = [];
216
217
const downloadUrl = await this.getDownloadUrl();
218
if (downloadUrl) {
219
additionalMessage = localize('check logs', "Please check the [log]({0}) for more details.", createCommandUri(showWindowLogActionId).toString());
220
promptChoices.push({
221
label: localize('download', "Try Downloading Manually..."),
222
run: () => this.openerService.open(downloadUrl).then(() => {
223
this.notificationService.prompt(
224
Severity.Info,
225
localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', this.extension.identifier.id),
226
[{
227
label: localize('installVSIX', "Install from VSIX..."),
228
run: () => this.commandService.executeCommand(SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID)
229
}]
230
);
231
})
232
});
233
}
234
235
const message = `${operationMessage}${additionalMessage ? ` ${additionalMessage}` : ''}`;
236
this.notificationService.prompt(Severity.Error, message, promptChoices);
237
}
238
239
private async getDownloadUrl(): Promise<URI | undefined> {
240
if (isIOS) {
241
return undefined;
242
}
243
if (!this.extension.gallery) {
244
return undefined;
245
}
246
if (!this.extensionManagementServerService.localExtensionManagementServer && !this.extensionManagementServerService.remoteExtensionManagementServer) {
247
return undefined;
248
}
249
let targetPlatform = this.extension.gallery.properties.targetPlatform;
250
if (targetPlatform !== TargetPlatform.UNIVERSAL && targetPlatform !== TargetPlatform.UNDEFINED && this.extensionManagementServerService.remoteExtensionManagementServer) {
251
try {
252
const manifest = await this.galleryService.getManifest(this.extension.gallery, CancellationToken.None);
253
if (manifest && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(manifest)) {
254
targetPlatform = await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getTargetPlatform();
255
}
256
} catch (error) {
257
this.logService.error(error);
258
return undefined;
259
}
260
}
261
if (targetPlatform === TargetPlatform.UNKNOWN) {
262
return undefined;
263
}
264
265
const [extension] = await this.galleryService.getExtensions([{
266
...this.extension.identifier,
267
version: this.version
268
}], {
269
targetPlatform
270
}, CancellationToken.None);
271
272
if (!extension) {
273
return undefined;
274
}
275
return URI.parse(extension.assets.download.uri);
276
}
277
278
}
279
280
export interface IExtensionActionChangeEvent extends IActionChangeEvent {
281
readonly hidden?: boolean;
282
readonly menuActions?: IAction[];
283
}
284
285
export abstract class ExtensionAction extends Action implements IExtensionContainer {
286
287
protected override _onDidChange = this._register(new Emitter<IExtensionActionChangeEvent>());
288
override get onDidChange() { return this._onDidChange.event; }
289
290
static readonly EXTENSION_ACTION_CLASS = 'extension-action';
291
static readonly TEXT_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} text`;
292
static readonly LABEL_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} label`;
293
static readonly ICON_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} icon`;
294
295
private _extension: IExtension | null = null;
296
get extension(): IExtension | null { return this._extension; }
297
set extension(extension: IExtension | null) { this._extension = extension; this.update(); }
298
299
private _hidden: boolean = false;
300
get hidden(): boolean { return this._hidden; }
301
set hidden(hidden: boolean) {
302
if (this._hidden !== hidden) {
303
this._hidden = hidden;
304
this._onDidChange.fire({ hidden });
305
}
306
}
307
308
protected override _setEnabled(value: boolean): void {
309
super._setEnabled(value);
310
if (this.hideOnDisabled) {
311
this.hidden = !value;
312
}
313
}
314
315
protected hideOnDisabled: boolean = true;
316
317
abstract update(): void;
318
}
319
320
export class ButtonWithDropDownExtensionAction extends ExtensionAction {
321
322
private primaryAction: IAction | undefined;
323
324
readonly menuActionClassNames: string[] = [];
325
private _menuActions: IAction[] = [];
326
get menuActions(): IAction[] { return [...this._menuActions]; }
327
328
override get extension(): IExtension | null {
329
return super.extension;
330
}
331
332
override set extension(extension: IExtension | null) {
333
this.extensionActions.forEach(a => a.extension = extension);
334
super.extension = extension;
335
}
336
337
protected readonly extensionActions: ExtensionAction[];
338
339
constructor(
340
id: string,
341
clazz: string,
342
private readonly actionsGroups: ExtensionAction[][],
343
) {
344
clazz = `${clazz} action-dropdown`;
345
super(id, undefined, clazz);
346
this.menuActionClassNames = clazz.split(' ');
347
this.hideOnDisabled = false;
348
this.extensionActions = actionsGroups.flat();
349
this.update();
350
this._register(Event.any(...this.extensionActions.map(a => a.onDidChange))(() => this.update(true)));
351
this.extensionActions.forEach(a => this._register(a));
352
}
353
354
update(donotUpdateActions?: boolean): void {
355
if (!donotUpdateActions) {
356
this.extensionActions.forEach(a => a.update());
357
}
358
359
const actionsGroups = this.actionsGroups.map(actionsGroup => actionsGroup.filter(a => !a.hidden));
360
361
let actions: IAction[] = [];
362
for (const visibleActions of actionsGroups) {
363
if (visibleActions.length) {
364
actions = [...actions, ...visibleActions, new Separator()];
365
}
366
}
367
actions = actions.length ? actions.slice(0, actions.length - 1) : actions;
368
369
this.primaryAction = actions[0];
370
this._menuActions = actions.length > 1 ? actions : [];
371
this._onDidChange.fire({ menuActions: this._menuActions });
372
373
if (this.primaryAction) {
374
this.hidden = false;
375
this.enabled = this.primaryAction.enabled;
376
this.label = this.getLabel(this.primaryAction as ExtensionAction);
377
this.tooltip = this.primaryAction.tooltip;
378
} else {
379
this.hidden = true;
380
this.enabled = false;
381
}
382
}
383
384
override async run(): Promise<void> {
385
if (this.enabled) {
386
await this.primaryAction?.run();
387
}
388
}
389
390
protected getLabel(action: ExtensionAction): string {
391
return action.label;
392
}
393
}
394
395
export class ButtonWithDropdownExtensionActionViewItem extends ActionWithDropdownActionViewItem {
396
397
constructor(
398
action: ButtonWithDropDownExtensionAction,
399
options: IActionViewItemOptions & IActionWithDropdownActionViewItemOptions,
400
contextMenuProvider: IContextMenuProvider
401
) {
402
super(null, action, options, contextMenuProvider);
403
this._register(action.onDidChange(e => {
404
if (e.hidden !== undefined || e.menuActions !== undefined) {
405
this.updateClass();
406
}
407
}));
408
}
409
410
override render(container: HTMLElement): void {
411
super.render(container);
412
this.updateClass();
413
}
414
415
protected override updateClass(): void {
416
super.updateClass();
417
if (this.element && this.dropdownMenuActionViewItem?.element) {
418
this.element.classList.toggle('hide', (<ButtonWithDropDownExtensionAction>this._action).hidden);
419
const isMenuEmpty = (<ButtonWithDropDownExtensionAction>this._action).menuActions.length === 0;
420
this.element.classList.toggle('empty', isMenuEmpty);
421
this.dropdownMenuActionViewItem.element.classList.toggle('hide', isMenuEmpty);
422
}
423
}
424
425
}
426
427
export class InstallAction extends ExtensionAction {
428
429
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent install`;
430
private static readonly HIDE = `${this.CLASS} hide`;
431
432
protected _manifest: IExtensionManifest | null = null;
433
set manifest(manifest: IExtensionManifest | null) {
434
this._manifest = manifest;
435
this.updateLabel();
436
}
437
438
private readonly updateThrottler = this._register(new Throttler());
439
public readonly options: InstallOptions;
440
441
constructor(
442
options: InstallOptions,
443
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
444
@IInstantiationService private readonly instantiationService: IInstantiationService,
445
@IExtensionService private readonly runtimeExtensionService: IExtensionService,
446
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
447
@ILabelService private readonly labelService: ILabelService,
448
@IDialogService private readonly dialogService: IDialogService,
449
@IPreferencesService private readonly preferencesService: IPreferencesService,
450
@ITelemetryService private readonly telemetryService: ITelemetryService,
451
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
452
@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,
453
@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService,
454
) {
455
super('extensions.install', localize('install', "Install"), InstallAction.CLASS, false);
456
this.hideOnDisabled = false;
457
this.options = { isMachineScoped: false, ...options };
458
this.update();
459
this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this.update()));
460
this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this));
461
}
462
463
update(): void {
464
this.updateThrottler.queue(() => this.computeAndUpdateEnablement());
465
}
466
467
protected async computeAndUpdateEnablement(): Promise<void> {
468
this.enabled = false;
469
this.class = InstallAction.HIDE;
470
this.hidden = true;
471
if (!this.extension) {
472
return;
473
}
474
if (this.extension.isBuiltin) {
475
return;
476
}
477
if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
478
return;
479
}
480
if (this.extension.state !== ExtensionState.Uninstalled) {
481
return;
482
}
483
if (this.options.installPreReleaseVersion && (!this.extension.hasPreReleaseVersion || this.allowedExtensionsService.isAllowed({ id: this.extension.identifier.id, publisherDisplayName: this.extension.publisherDisplayName, prerelease: true }) !== true)) {
484
return;
485
}
486
if (!this.options.installPreReleaseVersion && !this.extension.hasReleaseVersion) {
487
return;
488
}
489
this.hidden = false;
490
this.class = InstallAction.CLASS;
491
if (await this.extensionsWorkbenchService.canInstall(this.extension) === true) {
492
this.enabled = true;
493
this.updateLabel();
494
}
495
}
496
497
override async run(): Promise<any> {
498
if (!this.extension) {
499
return;
500
}
501
502
if (this.extension.gallery && !this.extension.gallery.isSigned && shouldRequireRepositorySignatureFor(this.extension.private, await this.extensionGalleryManifestService.getExtensionGalleryManifest())) {
503
const { result } = await this.dialogService.prompt({
504
type: Severity.Warning,
505
message: localize('not signed', "'{0}' is an extension from an unknown source. Are you sure you want to install?", this.extension.displayName),
506
detail: localize('not signed detail', "Extension is not signed."),
507
buttons: [
508
{
509
label: localize('install anyway', "Install Anyway"),
510
run: () => {
511
this.options.donotVerifySignature = true;
512
return true;
513
}
514
}
515
],
516
cancelButton: {
517
run: () => false
518
}
519
});
520
if (!result) {
521
return;
522
}
523
}
524
525
if (this.extension.deprecationInfo) {
526
let detail: string | MarkdownString = localize('deprecated message', "This extension is deprecated as it is no longer being maintained.");
527
enum DeprecationChoice {
528
InstallAnyway = 0,
529
ShowAlternateExtension = 1,
530
ConfigureSettings = 2,
531
Cancel = 3
532
}
533
const buttons: IPromptButton<DeprecationChoice>[] = [
534
{
535
label: localize('install anyway', "Install Anyway"),
536
run: () => DeprecationChoice.InstallAnyway
537
}
538
];
539
540
if (this.extension.deprecationInfo.extension) {
541
detail = localize('deprecated with alternate extension message', "This extension is deprecated. Use the {0} extension instead.", this.extension.deprecationInfo.extension.displayName);
542
543
const alternateExtension = this.extension.deprecationInfo.extension;
544
buttons.push({
545
label: localize({ key: 'Show alternate extension', comment: ['&& denotes a mnemonic'] }, "&&Open {0}", this.extension.deprecationInfo.extension.displayName),
546
run: async () => {
547
const [extension] = await this.extensionsWorkbenchService.getExtensions([{ id: alternateExtension.id, preRelease: alternateExtension.preRelease }], CancellationToken.None);
548
await this.extensionsWorkbenchService.open(extension);
549
550
return DeprecationChoice.ShowAlternateExtension;
551
}
552
});
553
} else if (this.extension.deprecationInfo.settings) {
554
detail = localize('deprecated with alternate settings message', "This extension is deprecated as this functionality is now built-in to VS Code.");
555
556
const settings = this.extension.deprecationInfo.settings;
557
buttons.push({
558
label: localize({ key: 'configure in settings', comment: ['&& denotes a mnemonic'] }, "&&Configure Settings"),
559
run: async () => {
560
await this.preferencesService.openSettings({ query: settings.map(setting => `@id:${setting}`).join(' ') });
561
562
return DeprecationChoice.ConfigureSettings;
563
}
564
});
565
} else if (this.extension.deprecationInfo.additionalInfo) {
566
detail = new MarkdownString(`${detail} ${this.extension.deprecationInfo.additionalInfo}`);
567
}
568
569
const { result } = await this.dialogService.prompt({
570
type: Severity.Warning,
571
message: localize('install confirmation', "Are you sure you want to install '{0}'?", this.extension.displayName),
572
detail: isString(detail) ? detail : undefined,
573
custom: isString(detail) ? undefined : {
574
markdownDetails: [{
575
markdown: detail
576
}]
577
},
578
buttons,
579
cancelButton: {
580
run: () => DeprecationChoice.Cancel
581
}
582
});
583
if (result !== DeprecationChoice.InstallAnyway) {
584
return;
585
}
586
}
587
588
this.extensionsWorkbenchService.open(this.extension, { showPreReleaseVersion: this.options.installPreReleaseVersion });
589
590
alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName));
591
592
/* __GDPR__
593
"extensions:action:install" : {
594
"owner": "sandy081",
595
"actionId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
596
"${include}": [
597
"${GalleryExtensionTelemetryData}"
598
]
599
}
600
*/
601
this.telemetryService.publicLog('extensions:action:install', { ...this.extension.telemetryData, actionId: this.id });
602
603
const extension = await this.install(this.extension);
604
605
if (extension?.local) {
606
alert(localize('installExtensionComplete', "Installing extension {0} is completed.", this.extension.displayName));
607
const runningExtension = await this.getRunningExtension(extension.local);
608
if (runningExtension && !(runningExtension.activationEvents && runningExtension.activationEvents.some(activationEent => activationEent.startsWith('onLanguage')))) {
609
const action = await this.getThemeAction(extension);
610
if (action) {
611
action.extension = extension;
612
try {
613
return action.run({ showCurrentTheme: true, ignoreFocusLost: true });
614
} finally {
615
action.dispose();
616
}
617
}
618
}
619
}
620
621
}
622
623
private async getThemeAction(extension: IExtension): Promise<ExtensionAction | undefined> {
624
const colorThemes = await this.workbenchThemeService.getColorThemes();
625
if (colorThemes.some(theme => isThemeFromExtension(theme, extension))) {
626
return this.instantiationService.createInstance(SetColorThemeAction);
627
}
628
const fileIconThemes = await this.workbenchThemeService.getFileIconThemes();
629
if (fileIconThemes.some(theme => isThemeFromExtension(theme, extension))) {
630
return this.instantiationService.createInstance(SetFileIconThemeAction);
631
}
632
const productIconThemes = await this.workbenchThemeService.getProductIconThemes();
633
if (productIconThemes.some(theme => isThemeFromExtension(theme, extension))) {
634
return this.instantiationService.createInstance(SetProductIconThemeAction);
635
}
636
return undefined;
637
}
638
639
private async install(extension: IExtension): Promise<IExtension | undefined> {
640
try {
641
return await this.extensionsWorkbenchService.install(extension, this.options);
642
} catch (error) {
643
await this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, this.options, extension.latestVersion, InstallOperation.Install, error).run();
644
return undefined;
645
}
646
}
647
648
private async getRunningExtension(extension: ILocalExtension): Promise<IExtensionDescription | null> {
649
const runningExtension = await this.runtimeExtensionService.getExtension(extension.identifier.id);
650
if (runningExtension) {
651
return runningExtension;
652
}
653
if (this.runtimeExtensionService.canAddExtension(toExtensionDescription(extension))) {
654
return new Promise<IExtensionDescription | null>((c, e) => {
655
const disposable = this.runtimeExtensionService.onDidChangeExtensions(async () => {
656
const runningExtension = await this.runtimeExtensionService.getExtension(extension.identifier.id);
657
if (runningExtension) {
658
disposable.dispose();
659
c(runningExtension);
660
}
661
});
662
});
663
}
664
return null;
665
}
666
667
protected updateLabel(): void {
668
this.label = this.getLabel();
669
}
670
671
getLabel(primary?: boolean): string {
672
if (this.extension?.isWorkspaceScoped && this.extension.resourceExtension && this.contextService.isInsideWorkspace(this.extension.resourceExtension.location)) {
673
return localize('install workspace version', "Install Workspace Extension");
674
}
675
/* install pre-release version */
676
if (this.options.installPreReleaseVersion && this.extension?.hasPreReleaseVersion) {
677
return primary ? localize('install pre-release', "Install Pre-Release") : localize('install pre-release version', "Install Pre-Release Version");
678
}
679
/* install released version that has a pre release version */
680
if (this.extension?.hasPreReleaseVersion) {
681
return primary ? localize('install', "Install") : localize('install release version', "Install Release Version");
682
}
683
return localize('install', "Install");
684
}
685
686
}
687
688
export class InstallDropdownAction extends ButtonWithDropDownExtensionAction {
689
690
set manifest(manifest: IExtensionManifest | null) {
691
this.extensionActions.forEach(a => (<InstallAction>a).manifest = manifest);
692
this.update();
693
}
694
695
constructor(
696
@IInstantiationService instantiationService: IInstantiationService,
697
@IWorkbenchExtensionManagementService extensionManagementService: IWorkbenchExtensionManagementService,
698
) {
699
super(`extensions.installActions`, InstallAction.CLASS, [
700
[
701
instantiationService.createInstance(InstallAction, { installPreReleaseVersion: extensionManagementService.preferPreReleases }),
702
instantiationService.createInstance(InstallAction, { installPreReleaseVersion: !extensionManagementService.preferPreReleases }),
703
]
704
]);
705
}
706
707
protected override getLabel(action: InstallAction): string {
708
return action.getLabel(true);
709
}
710
711
}
712
713
export class InstallingLabelAction extends ExtensionAction {
714
715
private static readonly LABEL = localize('installing', "Installing");
716
private static readonly CLASS = `${ExtensionAction.LABEL_ACTION_CLASS} install installing`;
717
718
constructor() {
719
super('extension.installing', InstallingLabelAction.LABEL, InstallingLabelAction.CLASS, false);
720
}
721
722
update(): void {
723
this.class = `${InstallingLabelAction.CLASS}${this.extension && this.extension.state === ExtensionState.Installing ? '' : ' hide'}`;
724
}
725
}
726
727
export abstract class InstallInOtherServerAction extends ExtensionAction {
728
729
protected static readonly INSTALL_LABEL = localize('install', "Install");
730
protected static readonly INSTALLING_LABEL = localize('installing', "Installing");
731
732
private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install-other-server`;
733
private static readonly InstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} install-other-server installing`;
734
735
updateWhenCounterExtensionChanges: boolean = true;
736
737
constructor(
738
id: string,
739
private readonly server: IExtensionManagementServer | null,
740
private readonly canInstallAnyWhere: boolean,
741
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
742
@IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService,
743
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
744
) {
745
super(id, InstallInOtherServerAction.INSTALL_LABEL, InstallInOtherServerAction.Class, false);
746
this.update();
747
}
748
749
update(): void {
750
this.enabled = false;
751
this.class = InstallInOtherServerAction.Class;
752
753
if (this.canInstall()) {
754
const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server === this.server)[0];
755
if (extensionInOtherServer) {
756
// Getting installed in other server
757
if (extensionInOtherServer.state === ExtensionState.Installing && !extensionInOtherServer.local) {
758
this.enabled = true;
759
this.label = InstallInOtherServerAction.INSTALLING_LABEL;
760
this.class = InstallInOtherServerAction.InstallingClass;
761
}
762
} else {
763
// Not installed in other server
764
this.enabled = true;
765
this.label = this.getInstallLabel();
766
}
767
}
768
}
769
770
protected canInstall(): boolean {
771
// Disable if extension is not installed or not an user extension
772
if (
773
!this.extension
774
|| !this.server
775
|| !this.extension.local
776
|| this.extension.state !== ExtensionState.Installed
777
|| this.extension.type !== ExtensionType.User
778
|| this.extension.enablementState === EnablementState.DisabledByEnvironment || this.extension.enablementState === EnablementState.DisabledByTrustRequirement || this.extension.enablementState === EnablementState.DisabledByVirtualWorkspace
779
) {
780
return false;
781
}
782
783
if (isLanguagePackExtension(this.extension.local.manifest)) {
784
return true;
785
}
786
787
// Prefers to run on UI
788
if (this.server === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local.manifest)) {
789
return true;
790
}
791
792
// Prefers to run on Workspace
793
if (this.server === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local.manifest)) {
794
return true;
795
}
796
797
// Prefers to run on Web
798
if (this.server === this.extensionManagementServerService.webExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWeb(this.extension.local.manifest)) {
799
return true;
800
}
801
802
if (this.canInstallAnyWhere) {
803
// Can run on UI
804
if (this.server === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnUI(this.extension.local.manifest)) {
805
return true;
806
}
807
808
// Can run on Workspace
809
if (this.server === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWorkspace(this.extension.local.manifest)) {
810
return true;
811
}
812
}
813
814
return false;
815
}
816
817
override async run(): Promise<void> {
818
if (!this.extension?.local) {
819
return;
820
}
821
if (!this.extension?.server) {
822
return;
823
}
824
if (!this.server) {
825
return;
826
}
827
this.extensionsWorkbenchService.open(this.extension);
828
alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName));
829
return this.extensionsWorkbenchService.installInServer(this.extension, this.server);
830
}
831
832
protected abstract getInstallLabel(): string;
833
}
834
835
export class RemoteInstallAction extends InstallInOtherServerAction {
836
837
constructor(
838
canInstallAnyWhere: boolean,
839
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
840
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
841
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
842
) {
843
super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService);
844
}
845
846
protected getInstallLabel(): string {
847
return this.extensionManagementServerService.remoteExtensionManagementServer
848
? localize({ key: 'install in remote', comment: ['This is the name of the action to install an extension in remote server. Placeholder is for the name of remote server.'] }, "Install in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label)
849
: InstallInOtherServerAction.INSTALL_LABEL;
850
}
851
852
}
853
854
export class LocalInstallAction extends InstallInOtherServerAction {
855
856
constructor(
857
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
858
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
859
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
860
) {
861
super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService);
862
}
863
864
protected getInstallLabel(): string {
865
return localize('install locally', "Install Locally");
866
}
867
868
}
869
870
export class WebInstallAction extends InstallInOtherServerAction {
871
872
constructor(
873
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
874
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
875
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
876
) {
877
super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService);
878
}
879
880
protected getInstallLabel(): string {
881
return localize('install browser', "Install in Browser");
882
}
883
884
}
885
886
export class UninstallAction extends ExtensionAction {
887
888
static readonly UninstallLabel = localize('uninstallAction', "Uninstall");
889
private static readonly UninstallingLabel = localize('Uninstalling', "Uninstalling");
890
891
static readonly UninstallClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall`;
892
private static readonly UnInstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall uninstalling`;
893
894
constructor(
895
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
896
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
897
@IDialogService private readonly dialogService: IDialogService
898
) {
899
super('extensions.uninstall', UninstallAction.UninstallLabel, UninstallAction.UninstallClass, false);
900
this.update();
901
}
902
903
update(): void {
904
if (!this.extension) {
905
this.enabled = false;
906
return;
907
}
908
909
const state = this.extension.state;
910
911
if (state === ExtensionState.Uninstalling) {
912
this.label = UninstallAction.UninstallingLabel;
913
this.class = UninstallAction.UnInstallingClass;
914
this.enabled = false;
915
return;
916
}
917
918
this.label = this.extension.local?.isApplicationScoped && this.userDataProfilesService.profiles.length > 1 ? localize('uninstallAll', "Uninstall (All Profiles)") : UninstallAction.UninstallLabel;
919
this.class = UninstallAction.UninstallClass;
920
this.tooltip = UninstallAction.UninstallLabel;
921
922
if (state !== ExtensionState.Installed) {
923
this.enabled = false;
924
return;
925
}
926
927
if (this.extension.isBuiltin) {
928
this.enabled = false;
929
return;
930
}
931
932
this.enabled = true;
933
}
934
935
override async run(): Promise<any> {
936
if (!this.extension) {
937
return;
938
}
939
alert(localize('uninstallExtensionStart', "Uninstalling extension {0} started.", this.extension.displayName));
940
941
try {
942
await this.extensionsWorkbenchService.uninstall(this.extension);
943
alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension.displayName));
944
} catch (error) {
945
if (!isCancellationError(error)) {
946
this.dialogService.error(getErrorMessage(error));
947
}
948
}
949
}
950
}
951
952
export class UpdateAction extends ExtensionAction {
953
954
private static readonly EnabledClass = `${this.LABEL_ACTION_CLASS} update`;
955
private static readonly DisabledClass = `${this.EnabledClass} disabled`;
956
957
private readonly updateThrottler = this._register(new Throttler());
958
959
constructor(
960
private readonly verbose: boolean,
961
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
962
@IDialogService private readonly dialogService: IDialogService,
963
@IOpenerService private readonly openerService: IOpenerService,
964
@IInstantiationService private readonly instantiationService: IInstantiationService,
965
) {
966
super(`extensions.update`, localize('update', "Update"), UpdateAction.DisabledClass, false);
967
this.update();
968
}
969
970
update(): void {
971
this.updateThrottler.queue(() => this.computeAndUpdateEnablement());
972
if (this.extension) {
973
this.label = this.verbose ? localize('update to', "Update to v{0}", this.extension.latestVersion) : localize('update', "Update");
974
}
975
}
976
977
private async computeAndUpdateEnablement(): Promise<void> {
978
this.enabled = false;
979
this.class = UpdateAction.DisabledClass;
980
981
if (!this.extension) {
982
return;
983
}
984
985
if (this.extension.deprecationInfo) {
986
return;
987
}
988
989
const canInstall = await this.extensionsWorkbenchService.canInstall(this.extension);
990
const isInstalled = this.extension.state === ExtensionState.Installed;
991
992
this.enabled = canInstall === true && isInstalled && this.extension.outdated;
993
this.class = this.enabled ? UpdateAction.EnabledClass : UpdateAction.DisabledClass;
994
}
995
996
override async run(): Promise<any> {
997
if (!this.extension) {
998
return;
999
}
1000
1001
const consent = await this.extensionsWorkbenchService.shouldRequireConsentToUpdate(this.extension);
1002
if (consent) {
1003
const { result } = await this.dialogService.prompt<'update' | 'review' | 'cancel'>({
1004
type: 'warning',
1005
title: localize('updateExtensionConsentTitle', "Update {0} Extension", this.extension.displayName),
1006
message: localize('updateExtensionConsent', "{0}\n\nWould you like to update the extension?", consent),
1007
buttons: [{
1008
label: localize('update', "Update"),
1009
run: () => 'update'
1010
}, {
1011
label: localize('review', "Review"),
1012
run: () => 'review'
1013
}, {
1014
label: localize('cancel', "Cancel"),
1015
run: () => 'cancel'
1016
}]
1017
});
1018
if (result === 'cancel') {
1019
return;
1020
}
1021
if (result === 'review') {
1022
if (this.extension.hasChangelog()) {
1023
return this.extensionsWorkbenchService.open(this.extension, { tab: ExtensionEditorTab.Changelog });
1024
}
1025
if (this.extension.repository) {
1026
return this.openerService.open(this.extension.repository);
1027
}
1028
return this.extensionsWorkbenchService.open(this.extension);
1029
}
1030
}
1031
1032
const installOptions: InstallOptions = {};
1033
if (this.extension.local?.source === 'vsix' && this.extension.local.pinned) {
1034
installOptions.pinned = false;
1035
}
1036
if (this.extension.local?.preRelease) {
1037
installOptions.installPreReleaseVersion = true;
1038
}
1039
try {
1040
alert(localize('updateExtensionStart', "Updating extension {0} to version {1} started.", this.extension.displayName, this.extension.latestVersion));
1041
await this.extensionsWorkbenchService.install(this.extension, installOptions);
1042
alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", this.extension.displayName, this.extension.latestVersion));
1043
} catch (err) {
1044
this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension, installOptions, this.extension.latestVersion, InstallOperation.Update, err).run();
1045
}
1046
}
1047
}
1048
1049
export class ToggleAutoUpdateForExtensionAction extends ExtensionAction {
1050
1051
static readonly ID = 'workbench.extensions.action.toggleAutoUpdateForExtension';
1052
static readonly LABEL = localize2('enableAutoUpdateLabel', "Auto Update");
1053
1054
private static readonly EnabledClass = `${ExtensionAction.EXTENSION_ACTION_CLASS} auto-update`;
1055
private static readonly DisabledClass = `${this.EnabledClass} hide`;
1056
1057
constructor(
1058
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
1059
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
1060
@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,
1061
@IConfigurationService configurationService: IConfigurationService,
1062
) {
1063
super(ToggleAutoUpdateForExtensionAction.ID, ToggleAutoUpdateForExtensionAction.LABEL.value, ToggleAutoUpdateForExtensionAction.DisabledClass);
1064
this._register(configurationService.onDidChangeConfiguration(e => {
1065
if (e.affectsConfiguration(AutoUpdateConfigurationKey)) {
1066
this.update();
1067
}
1068
}));
1069
this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(e => this.update()));
1070
this.update();
1071
}
1072
1073
override update() {
1074
this.enabled = false;
1075
this.class = ToggleAutoUpdateForExtensionAction.DisabledClass;
1076
if (!this.extension) {
1077
return;
1078
}
1079
if (this.extension.isBuiltin) {
1080
return;
1081
}
1082
if (this.extension.deprecationInfo?.disallowInstall) {
1083
return;
1084
}
1085
1086
const extension = this.extension.local ?? this.extension.gallery;
1087
if (extension && this.allowedExtensionsService.isAllowed(extension) !== true) {
1088
return;
1089
}
1090
if (this.extensionsWorkbenchService.getAutoUpdateValue() === 'onlyEnabledExtensions' && !this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState)) {
1091
return;
1092
}
1093
this.enabled = true;
1094
this.class = ToggleAutoUpdateForExtensionAction.EnabledClass;
1095
this.checked = this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension);
1096
}
1097
1098
override async run(): Promise<any> {
1099
if (!this.extension) {
1100
return;
1101
}
1102
1103
const enableAutoUpdate = !this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension);
1104
await this.extensionsWorkbenchService.updateAutoUpdateEnablementFor(this.extension, enableAutoUpdate);
1105
1106
if (enableAutoUpdate) {
1107
alert(localize('enableAutoUpdate', "Enabled auto updates for", this.extension.displayName));
1108
} else {
1109
alert(localize('disableAutoUpdate', "Disabled auto updates for", this.extension.displayName));
1110
}
1111
}
1112
}
1113
1114
export class ToggleAutoUpdatesForPublisherAction extends ExtensionAction {
1115
1116
static readonly ID = 'workbench.extensions.action.toggleAutoUpdatesForPublisher';
1117
static readonly LABEL = localize('toggleAutoUpdatesForPublisherLabel', "Auto Update All (From Publisher)");
1118
1119
constructor(
1120
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService
1121
) {
1122
super(ToggleAutoUpdatesForPublisherAction.ID, ToggleAutoUpdatesForPublisherAction.LABEL);
1123
}
1124
1125
override update() { }
1126
1127
override async run(): Promise<any> {
1128
if (!this.extension) {
1129
return;
1130
}
1131
alert(localize('ignoreExtensionUpdatePublisher', "Ignoring updates published by {0}.", this.extension.publisherDisplayName));
1132
const enableAutoUpdate = !this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension.publisher);
1133
await this.extensionsWorkbenchService.updateAutoUpdateEnablementFor(this.extension.publisher, enableAutoUpdate);
1134
if (enableAutoUpdate) {
1135
alert(localize('enableAutoUpdate', "Enabled auto updates for", this.extension.displayName));
1136
} else {
1137
alert(localize('disableAutoUpdate', "Disabled auto updates for", this.extension.displayName));
1138
}
1139
}
1140
}
1141
1142
export class MigrateDeprecatedExtensionAction extends ExtensionAction {
1143
1144
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} migrate`;
1145
private static readonly DisabledClass = `${this.EnabledClass} disabled`;
1146
1147
constructor(
1148
private readonly small: boolean,
1149
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
1150
) {
1151
super('extensionsAction.migrateDeprecatedExtension', localize('migrateExtension', "Migrate"), MigrateDeprecatedExtensionAction.DisabledClass, false);
1152
this.update();
1153
}
1154
1155
update(): void {
1156
this.enabled = false;
1157
this.class = MigrateDeprecatedExtensionAction.DisabledClass;
1158
if (!this.extension?.local) {
1159
return;
1160
}
1161
if (this.extension.state !== ExtensionState.Installed) {
1162
return;
1163
}
1164
if (!this.extension.deprecationInfo?.extension) {
1165
return;
1166
}
1167
const id = this.extension.deprecationInfo.extension.id;
1168
if (this.extensionsWorkbenchService.local.some(e => areSameExtensions(e.identifier, { id }))) {
1169
return;
1170
}
1171
this.enabled = true;
1172
this.class = MigrateDeprecatedExtensionAction.EnabledClass;
1173
this.tooltip = localize('migrate to', "Migrate to {0}", this.extension.deprecationInfo.extension.displayName);
1174
this.label = this.small ? localize('migrate', "Migrate") : this.tooltip;
1175
}
1176
1177
override async run(): Promise<any> {
1178
if (!this.extension?.deprecationInfo?.extension) {
1179
return;
1180
}
1181
const local = this.extension.local;
1182
await this.extensionsWorkbenchService.uninstall(this.extension);
1183
const [extension] = await this.extensionsWorkbenchService.getExtensions([{ id: this.extension.deprecationInfo.extension.id, preRelease: this.extension.deprecationInfo?.extension?.preRelease }], CancellationToken.None);
1184
await this.extensionsWorkbenchService.install(extension, { isMachineScoped: local?.isMachineScoped });
1185
}
1186
}
1187
1188
export abstract class DropDownExtensionAction extends ExtensionAction {
1189
1190
constructor(
1191
id: string,
1192
label: string,
1193
cssClass: string,
1194
enabled: boolean,
1195
@IInstantiationService protected instantiationService: IInstantiationService
1196
) {
1197
super(id, label, cssClass, enabled);
1198
}
1199
1200
private _actionViewItem: DropDownExtensionActionViewItem | null = null;
1201
createActionViewItem(options: IActionViewItemOptions): DropDownExtensionActionViewItem {
1202
this._actionViewItem = this.instantiationService.createInstance(DropDownExtensionActionViewItem, this, options);
1203
return this._actionViewItem;
1204
}
1205
1206
public override run(actionGroups: IAction[][]): Promise<any> {
1207
this._actionViewItem?.showMenu(actionGroups);
1208
return Promise.resolve();
1209
}
1210
}
1211
1212
export class DropDownExtensionActionViewItem extends ActionViewItem {
1213
1214
constructor(
1215
action: IAction,
1216
options: IActionViewItemOptions,
1217
@IContextMenuService private readonly contextMenuService: IContextMenuService
1218
) {
1219
super(null, action, { ...options, icon: true, label: true });
1220
}
1221
1222
public showMenu(menuActionGroups: IAction[][]): void {
1223
if (this.element) {
1224
const actions = this.getActions(menuActionGroups);
1225
const elementPosition = DOM.getDomNodePagePosition(this.element);
1226
const anchor = { x: elementPosition.left, y: elementPosition.top + elementPosition.height + 10 };
1227
this.contextMenuService.showContextMenu({
1228
getAnchor: () => anchor,
1229
getActions: () => actions,
1230
actionRunner: this.actionRunner,
1231
onHide: () => disposeIfDisposable(actions)
1232
});
1233
}
1234
}
1235
1236
private getActions(menuActionGroups: IAction[][]): IAction[] {
1237
let actions: IAction[] = [];
1238
for (const menuActions of menuActionGroups) {
1239
actions = [...actions, ...menuActions, new Separator()];
1240
}
1241
return actions.length ? actions.slice(0, actions.length - 1) : actions;
1242
}
1243
}
1244
1245
async function getContextMenuActionsGroups(extension: IExtension | undefined | null, contextKeyService: IContextKeyService, instantiationService: IInstantiationService): Promise<[string, Array<MenuItemAction | SubmenuItemAction>][]> {
1246
return instantiationService.invokeFunction(async accessor => {
1247
const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
1248
const extensionEnablementService = accessor.get(IWorkbenchExtensionEnablementService);
1249
const menuService = accessor.get(IMenuService);
1250
const extensionRecommendationsService = accessor.get(IExtensionRecommendationsService);
1251
const extensionIgnoredRecommendationsService = accessor.get(IExtensionIgnoredRecommendationsService);
1252
const workbenchThemeService = accessor.get(IWorkbenchThemeService);
1253
const authenticationUsageService = accessor.get(IAuthenticationUsageService);
1254
const allowedExtensionsService = accessor.get(IAllowedExtensionsService);
1255
const cksOverlay: [string, any][] = [];
1256
1257
if (extension) {
1258
cksOverlay.push(['extension', extension.identifier.id]);
1259
cksOverlay.push(['isBuiltinExtension', extension.isBuiltin]);
1260
cksOverlay.push(['isDefaultApplicationScopedExtension', extension.local && isApplicationScopedExtension(extension.local.manifest)]);
1261
cksOverlay.push(['isApplicationScopedExtension', extension.local && extension.local.isApplicationScoped]);
1262
cksOverlay.push(['isWorkspaceScopedExtension', extension.isWorkspaceScoped]);
1263
cksOverlay.push(['isGalleryExtension', !!extension.identifier.uuid]);
1264
if (extension.local) {
1265
cksOverlay.push(['extensionSource', extension.local.source]);
1266
}
1267
cksOverlay.push(['extensionHasConfiguration', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes.configuration]);
1268
cksOverlay.push(['extensionHasKeybindings', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes.keybindings]);
1269
cksOverlay.push(['extensionHasCommands', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes?.commands]);
1270
cksOverlay.push(['isExtensionRecommended', !!extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]]);
1271
cksOverlay.push(['isExtensionWorkspaceRecommended', extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]?.reasonId === ExtensionRecommendationReason.Workspace]);
1272
cksOverlay.push(['isUserIgnoredRecommendation', extensionIgnoredRecommendationsService.globalIgnoredRecommendations.some(e => e === extension.identifier.id.toLowerCase())]);
1273
cksOverlay.push(['isExtensionPinned', extension.pinned]);
1274
cksOverlay.push(['isExtensionEnabled', extensionEnablementService.isEnabledEnablementState(extension.enablementState)]);
1275
switch (extension.state) {
1276
case ExtensionState.Installing:
1277
cksOverlay.push(['extensionStatus', 'installing']);
1278
break;
1279
case ExtensionState.Installed:
1280
cksOverlay.push(['extensionStatus', 'installed']);
1281
break;
1282
case ExtensionState.Uninstalling:
1283
cksOverlay.push(['extensionStatus', 'uninstalling']);
1284
break;
1285
case ExtensionState.Uninstalled:
1286
cksOverlay.push(['extensionStatus', 'uninstalled']);
1287
break;
1288
}
1289
cksOverlay.push(['installedExtensionIsPreReleaseVersion', !!extension.local?.isPreReleaseVersion]);
1290
cksOverlay.push(['installedExtensionIsOptedToPreRelease', !!extension.local?.preRelease]);
1291
cksOverlay.push(['galleryExtensionIsPreReleaseVersion', !!extension.gallery?.properties.isPreReleaseVersion]);
1292
cksOverlay.push(['galleryExtensionHasPreReleaseVersion', extension.gallery?.hasPreReleaseVersion]);
1293
cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]);
1294
cksOverlay.push(['extensionHasReleaseVersion', extension.hasReleaseVersion]);
1295
cksOverlay.push(['extensionDisallowInstall', extension.isMalicious || extension.deprecationInfo?.disallowInstall]);
1296
cksOverlay.push(['isExtensionAllowed', allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }) === true]);
1297
cksOverlay.push(['isPreReleaseExtensionAllowed', allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName, prerelease: true }) === true]);
1298
cksOverlay.push(['extensionIsUnsigned', extension.gallery && !extension.gallery.isSigned]);
1299
cksOverlay.push(['extensionIsPrivate', extension.gallery?.private]);
1300
1301
const [colorThemes, fileIconThemes, productIconThemes, extensionUsesAuth] = await Promise.all([workbenchThemeService.getColorThemes(), workbenchThemeService.getFileIconThemes(), workbenchThemeService.getProductIconThemes(), authenticationUsageService.extensionUsesAuth(extension.identifier.id.toLowerCase())]);
1302
cksOverlay.push(['extensionHasColorThemes', colorThemes.some(theme => isThemeFromExtension(theme, extension))]);
1303
cksOverlay.push(['extensionHasFileIconThemes', fileIconThemes.some(theme => isThemeFromExtension(theme, extension))]);
1304
cksOverlay.push(['extensionHasProductIconThemes', productIconThemes.some(theme => isThemeFromExtension(theme, extension))]);
1305
cksOverlay.push(['extensionHasAccountPreferences', extensionUsesAuth]);
1306
1307
cksOverlay.push(['canSetLanguage', extensionsWorkbenchService.canSetLanguage(extension)]);
1308
cksOverlay.push(['isActiveLanguagePackExtension', extension.gallery && language === getLocale(extension.gallery)]);
1309
}
1310
1311
const actionsGroups = menuService.getMenuActions(MenuId.ExtensionContext, contextKeyService.createOverlay(cksOverlay), { shouldForwardArgs: true });
1312
return actionsGroups;
1313
});
1314
}
1315
1316
function toActions(actionsGroups: [string, Array<MenuItemAction | SubmenuItemAction>][], instantiationService: IInstantiationService): IAction[][] {
1317
const result: IAction[][] = [];
1318
for (const [, actions] of actionsGroups) {
1319
result.push(actions.map(action => {
1320
if (action instanceof SubmenuAction) {
1321
return action;
1322
}
1323
return instantiationService.createInstance(MenuItemExtensionAction, action);
1324
}));
1325
}
1326
return result;
1327
}
1328
1329
1330
export async function getContextMenuActions(extension: IExtension | undefined | null, contextKeyService: IContextKeyService, instantiationService: IInstantiationService): Promise<IAction[][]> {
1331
const actionsGroups = await getContextMenuActionsGroups(extension, contextKeyService, instantiationService);
1332
return toActions(actionsGroups, instantiationService);
1333
}
1334
1335
export class ManageExtensionAction extends DropDownExtensionAction {
1336
1337
static readonly ID = 'extensions.manage';
1338
1339
private static readonly Class = `${ExtensionAction.ICON_ACTION_CLASS} manage ` + ThemeIcon.asClassName(manageExtensionIcon);
1340
private static readonly HideManageExtensionClass = `${this.Class} hide`;
1341
1342
constructor(
1343
@IInstantiationService instantiationService: IInstantiationService,
1344
@IExtensionService private readonly extensionService: IExtensionService,
1345
@IContextKeyService private readonly contextKeyService: IContextKeyService,
1346
) {
1347
1348
super(ManageExtensionAction.ID, '', '', true, instantiationService);
1349
1350
this.tooltip = localize('manage', "Manage");
1351
1352
this.update();
1353
}
1354
1355
async getActionGroups(): Promise<IAction[][]> {
1356
const groups: IAction[][] = [];
1357
const contextMenuActionsGroups = await getContextMenuActionsGroups(this.extension, this.contextKeyService, this.instantiationService);
1358
const themeActions: IAction[] = [], installActions: IAction[] = [], updateActions: IAction[] = [], otherActionGroups: IAction[][] = [];
1359
for (const [group, actions] of contextMenuActionsGroups) {
1360
if (group === INSTALL_ACTIONS_GROUP) {
1361
installActions.push(...toActions([[group, actions]], this.instantiationService)[0]);
1362
} else if (group === UPDATE_ACTIONS_GROUP) {
1363
updateActions.push(...toActions([[group, actions]], this.instantiationService)[0]);
1364
} else if (group === THEME_ACTIONS_GROUP) {
1365
themeActions.push(...toActions([[group, actions]], this.instantiationService)[0]);
1366
} else {
1367
otherActionGroups.push(...toActions([[group, actions]], this.instantiationService));
1368
}
1369
}
1370
1371
if (themeActions.length) {
1372
groups.push(themeActions);
1373
}
1374
1375
groups.push([
1376
this.instantiationService.createInstance(EnableGloballyAction),
1377
this.instantiationService.createInstance(EnableForWorkspaceAction)
1378
]);
1379
groups.push([
1380
this.instantiationService.createInstance(DisableGloballyAction),
1381
this.instantiationService.createInstance(DisableForWorkspaceAction)
1382
]);
1383
if (updateActions.length) {
1384
groups.push(updateActions);
1385
}
1386
groups.push([
1387
...(installActions.length ? installActions : []),
1388
this.instantiationService.createInstance(InstallAnotherVersionAction, this.extension, false),
1389
this.instantiationService.createInstance(UninstallAction),
1390
]);
1391
1392
otherActionGroups.forEach(actions => groups.push(actions));
1393
1394
groups.forEach(group => group.forEach(extensionAction => {
1395
if (extensionAction instanceof ExtensionAction) {
1396
extensionAction.extension = this.extension;
1397
}
1398
}));
1399
1400
return groups;
1401
}
1402
1403
override async run(): Promise<any> {
1404
await this.extensionService.whenInstalledExtensionsRegistered();
1405
return super.run(await this.getActionGroups());
1406
}
1407
1408
update(): void {
1409
this.class = ManageExtensionAction.HideManageExtensionClass;
1410
this.enabled = false;
1411
if (this.extension) {
1412
const state = this.extension.state;
1413
this.enabled = state === ExtensionState.Installed;
1414
this.class = this.enabled || state === ExtensionState.Uninstalling ? ManageExtensionAction.Class : ManageExtensionAction.HideManageExtensionClass;
1415
}
1416
}
1417
}
1418
1419
export class ExtensionEditorManageExtensionAction extends DropDownExtensionAction {
1420
1421
constructor(
1422
private readonly contextKeyService: IContextKeyService,
1423
instantiationService: IInstantiationService
1424
) {
1425
super('extensionEditor.manageExtension', '', `${ExtensionAction.ICON_ACTION_CLASS} manage ${ThemeIcon.asClassName(manageExtensionIcon)}`, true, instantiationService);
1426
this.tooltip = localize('manage', "Manage");
1427
}
1428
1429
update(): void { }
1430
1431
override async run(): Promise<any> {
1432
const actionGroups: IAction[][] = [];
1433
(await getContextMenuActions(this.extension, this.contextKeyService, this.instantiationService)).forEach(actions => actionGroups.push(actions));
1434
actionGroups.forEach(group => group.forEach(extensionAction => {
1435
if (extensionAction instanceof ExtensionAction) {
1436
extensionAction.extension = this.extension;
1437
}
1438
}));
1439
return super.run(actionGroups);
1440
}
1441
1442
}
1443
1444
export class MenuItemExtensionAction extends ExtensionAction {
1445
1446
constructor(
1447
private readonly action: IAction,
1448
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
1449
) {
1450
super(action.id, action.label);
1451
}
1452
1453
override get enabled(): boolean {
1454
return this.action.enabled;
1455
}
1456
1457
override set enabled(value: boolean) {
1458
this.action.enabled = value;
1459
}
1460
1461
update() {
1462
if (!this.extension) {
1463
return;
1464
}
1465
if (this.action.id === TOGGLE_IGNORE_EXTENSION_ACTION_ID) {
1466
this.checked = !this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension);
1467
} else if (this.action.id === ToggleAutoUpdateForExtensionAction.ID) {
1468
this.checked = this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension);
1469
} else if (this.action.id === ToggleAutoUpdatesForPublisherAction.ID) {
1470
this.checked = this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension.publisher);
1471
} else {
1472
this.checked = this.action.checked;
1473
}
1474
}
1475
1476
override async run(): Promise<void> {
1477
if (this.extension) {
1478
const id = this.extension.local ? getExtensionId(this.extension.local.manifest.publisher, this.extension.local.manifest.name)
1479
: this.extension.gallery ? getExtensionId(this.extension.gallery.publisher, this.extension.gallery.name)
1480
: this.extension.identifier.id;
1481
const extensionArg: IExtensionArg = {
1482
id: this.extension.identifier.id,
1483
version: this.extension.version,
1484
location: this.extension.local?.location,
1485
galleryLink: this.extension.url
1486
};
1487
await this.action.run(id, extensionArg);
1488
}
1489
}
1490
}
1491
1492
export class TogglePreReleaseExtensionAction extends ExtensionAction {
1493
1494
static readonly ID = 'workbench.extensions.action.togglePreRlease';
1495
static readonly LABEL = localize('togglePreRleaseLabel', "Pre-Release");
1496
1497
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} prominent pre-release`;
1498
private static readonly DisabledClass = `${this.EnabledClass} hide`;
1499
1500
constructor(
1501
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
1502
@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,
1503
) {
1504
super(TogglePreReleaseExtensionAction.ID, TogglePreReleaseExtensionAction.LABEL, TogglePreReleaseExtensionAction.DisabledClass);
1505
this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this.update()));
1506
this.update();
1507
}
1508
1509
override update() {
1510
this.enabled = false;
1511
this.class = TogglePreReleaseExtensionAction.DisabledClass;
1512
if (!this.extension) {
1513
return;
1514
}
1515
if (this.extension.isBuiltin) {
1516
return;
1517
}
1518
if (this.extension.state !== ExtensionState.Installed) {
1519
return;
1520
}
1521
if (!this.extension.hasPreReleaseVersion) {
1522
return;
1523
}
1524
if (!this.extension.gallery) {
1525
return;
1526
}
1527
if (this.extension.preRelease) {
1528
if (!this.extension.isPreReleaseVersion) {
1529
return;
1530
}
1531
if (this.allowedExtensionsService.isAllowed({ id: this.extension.identifier.id, publisherDisplayName: this.extension.publisherDisplayName }) !== true) {
1532
return;
1533
}
1534
}
1535
if (!this.extension.preRelease) {
1536
if (!this.extension.gallery.hasPreReleaseVersion) {
1537
return;
1538
}
1539
if (this.allowedExtensionsService.isAllowed(this.extension.gallery) !== true) {
1540
return;
1541
}
1542
}
1543
this.enabled = true;
1544
this.class = TogglePreReleaseExtensionAction.EnabledClass;
1545
1546
if (this.extension.preRelease) {
1547
this.label = localize('togglePreRleaseDisableLabel', "Switch to Release Version");
1548
this.tooltip = localize('togglePreRleaseDisableTooltip', "This will switch and enable updates to release versions");
1549
} else {
1550
this.label = localize('switchToPreReleaseLabel', "Switch to Pre-Release Version");
1551
this.tooltip = localize('switchToPreReleaseTooltip', "This will switch to pre-release version and enable updates to latest version always");
1552
}
1553
}
1554
1555
override async run(): Promise<any> {
1556
if (!this.extension) {
1557
return;
1558
}
1559
this.extensionsWorkbenchService.open(this.extension, { showPreReleaseVersion: !this.extension.preRelease });
1560
await this.extensionsWorkbenchService.togglePreRelease(this.extension);
1561
}
1562
}
1563
1564
export class InstallAnotherVersionAction extends ExtensionAction {
1565
1566
static readonly ID = 'workbench.extensions.action.install.anotherVersion';
1567
static readonly LABEL = localize('install another version', "Install Specific Version...");
1568
1569
constructor(
1570
extension: IExtension | null,
1571
private readonly whenInstalled: boolean,
1572
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
1573
@IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService,
1574
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
1575
@IQuickInputService private readonly quickInputService: IQuickInputService,
1576
@IInstantiationService private readonly instantiationService: IInstantiationService,
1577
@IDialogService private readonly dialogService: IDialogService,
1578
@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,
1579
) {
1580
super(InstallAnotherVersionAction.ID, InstallAnotherVersionAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS);
1581
this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this.update()));
1582
this.extension = extension;
1583
this.update();
1584
}
1585
1586
update(): void {
1587
this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.identifier.uuid && !this.extension.deprecationInfo
1588
&& this.allowedExtensionsService.isAllowed({ id: this.extension.identifier.id, publisherDisplayName: this.extension.publisherDisplayName }) === true;
1589
if (this.enabled && this.whenInstalled) {
1590
this.enabled = !!this.extension?.local && !!this.extension.server && this.extension.state === ExtensionState.Installed;
1591
}
1592
}
1593
1594
override async run(): Promise<any> {
1595
if (!this.enabled) {
1596
return;
1597
}
1598
if (!this.extension) {
1599
return;
1600
}
1601
const targetPlatform = this.extension.server ? await this.extension.server.extensionManagementService.getTargetPlatform() : await this.extensionManagementService.getTargetPlatform();
1602
const allVersions = await this.extensionGalleryService.getAllCompatibleVersions(this.extension.identifier, this.extension.local?.preRelease ?? this.extension.gallery?.properties.isPreReleaseVersion ?? false, targetPlatform);
1603
if (!allVersions.length) {
1604
await this.dialogService.info(localize('no versions', "This extension has no other versions."));
1605
return;
1606
}
1607
1608
const picks = allVersions.map((v, i) => {
1609
return {
1610
id: v.version,
1611
label: v.version,
1612
description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${localize('pre-release', "pre-release")})` : ''}${v.version === this.extension?.local?.manifest.version ? ` (${localize('current', "current")})` : ''}`,
1613
ariaLabel: `${v.isPreReleaseVersion ? 'Pre-Release version' : 'Release version'} ${v.version}`,
1614
isPreReleaseVersion: v.isPreReleaseVersion
1615
};
1616
});
1617
const pick = await this.quickInputService.pick(picks,
1618
{
1619
placeHolder: localize('selectVersion', "Select Version to Install"),
1620
matchOnDetail: true
1621
});
1622
if (pick) {
1623
if (this.extension.local?.manifest.version === pick.id) {
1624
return;
1625
}
1626
const options = { installPreReleaseVersion: pick.isPreReleaseVersion, version: pick.id };
1627
try {
1628
await this.extensionsWorkbenchService.install(this.extension, options);
1629
} catch (error) {
1630
this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension, options, pick.id, InstallOperation.Install, error).run();
1631
}
1632
}
1633
return null;
1634
}
1635
1636
}
1637
1638
export class EnableForWorkspaceAction extends ExtensionAction {
1639
1640
static readonly ID = 'extensions.enableForWorkspace';
1641
static readonly LABEL = localize('enableForWorkspaceAction', "Enable (Workspace)");
1642
1643
constructor(
1644
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
1645
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService
1646
) {
1647
super(EnableForWorkspaceAction.ID, EnableForWorkspaceAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS);
1648
this.tooltip = localize('enableForWorkspaceActionToolTip', "Enable this extension only in this workspace");
1649
this.update();
1650
}
1651
1652
update(): void {
1653
this.enabled = false;
1654
if (this.extension && this.extension.local && !this.extension.isWorkspaceScoped) {
1655
this.enabled = this.extension.state === ExtensionState.Installed
1656
&& !this.extensionEnablementService.isEnabled(this.extension.local)
1657
&& this.extensionEnablementService.canChangeWorkspaceEnablement(this.extension.local);
1658
}
1659
}
1660
1661
override async run(): Promise<any> {
1662
if (!this.extension) {
1663
return;
1664
}
1665
return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.EnabledWorkspace);
1666
}
1667
}
1668
1669
export class EnableGloballyAction extends ExtensionAction {
1670
1671
static readonly ID = 'extensions.enableGlobally';
1672
static readonly LABEL = localize('enableGloballyAction', "Enable");
1673
1674
constructor(
1675
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
1676
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService
1677
) {
1678
super(EnableGloballyAction.ID, EnableGloballyAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS);
1679
this.tooltip = localize('enableGloballyActionToolTip', "Enable this extension");
1680
this.update();
1681
}
1682
1683
update(): void {
1684
this.enabled = false;
1685
if (this.extension && this.extension.local && !this.extension.isWorkspaceScoped) {
1686
this.enabled = this.extension.state === ExtensionState.Installed
1687
&& this.extensionEnablementService.isDisabledGlobally(this.extension.local)
1688
&& this.extensionEnablementService.canChangeEnablement(this.extension.local);
1689
}
1690
}
1691
1692
override async run(): Promise<any> {
1693
if (!this.extension) {
1694
return;
1695
}
1696
return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.EnabledGlobally);
1697
}
1698
}
1699
1700
export class DisableForWorkspaceAction extends ExtensionAction {
1701
1702
static readonly ID = 'extensions.disableForWorkspace';
1703
static readonly LABEL = localize('disableForWorkspaceAction', "Disable (Workspace)");
1704
1705
constructor(
1706
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
1707
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
1708
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
1709
@IExtensionService private readonly extensionService: IExtensionService,
1710
) {
1711
super(DisableForWorkspaceAction.ID, DisableForWorkspaceAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS);
1712
this.tooltip = localize('disableForWorkspaceActionToolTip', "Disable this extension only in this workspace");
1713
this.update();
1714
this._register(this.extensionService.onDidChangeExtensions(() => this.update()));
1715
}
1716
1717
update(): void {
1718
this.enabled = false;
1719
if (this.extension && this.extension.local && !this.extension.isWorkspaceScoped && this.extensionService.extensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) {
1720
this.enabled = this.extension.state === ExtensionState.Installed
1721
&& (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace)
1722
&& this.extensionEnablementService.canChangeWorkspaceEnablement(this.extension.local);
1723
}
1724
}
1725
1726
override async run(): Promise<any> {
1727
if (!this.extension) {
1728
return;
1729
}
1730
return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.DisabledWorkspace);
1731
}
1732
}
1733
1734
export class DisableGloballyAction extends ExtensionAction {
1735
1736
static readonly ID = 'extensions.disableGlobally';
1737
static readonly LABEL = localize('disableGloballyAction', "Disable");
1738
1739
constructor(
1740
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
1741
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
1742
@IExtensionService private readonly extensionService: IExtensionService,
1743
) {
1744
super(DisableGloballyAction.ID, DisableGloballyAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS);
1745
this.tooltip = localize('disableGloballyActionToolTip', "Disable this extension");
1746
this.update();
1747
this._register(this.extensionService.onDidChangeExtensions(() => this.update()));
1748
}
1749
1750
update(): void {
1751
this.enabled = false;
1752
if (this.extension && this.extension.local && !this.extension.isWorkspaceScoped && this.extensionService.extensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))) {
1753
this.enabled = this.extension.state === ExtensionState.Installed
1754
&& (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace)
1755
&& this.extensionEnablementService.canChangeEnablement(this.extension.local);
1756
}
1757
}
1758
1759
override async run(): Promise<any> {
1760
if (!this.extension) {
1761
return;
1762
}
1763
return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.DisabledGlobally);
1764
}
1765
}
1766
1767
export class EnableDropDownAction extends ButtonWithDropDownExtensionAction {
1768
1769
constructor(
1770
@IInstantiationService instantiationService: IInstantiationService
1771
) {
1772
super('extensions.enable', ExtensionAction.LABEL_ACTION_CLASS, [
1773
[
1774
instantiationService.createInstance(EnableGloballyAction),
1775
instantiationService.createInstance(EnableForWorkspaceAction)
1776
]
1777
]);
1778
}
1779
}
1780
1781
export class DisableDropDownAction extends ButtonWithDropDownExtensionAction {
1782
1783
constructor(
1784
@IInstantiationService instantiationService: IInstantiationService
1785
) {
1786
super('extensions.disable', ExtensionAction.LABEL_ACTION_CLASS, [[
1787
instantiationService.createInstance(DisableGloballyAction),
1788
instantiationService.createInstance(DisableForWorkspaceAction)
1789
]]);
1790
}
1791
1792
}
1793
1794
export class ExtensionRuntimeStateAction extends ExtensionAction {
1795
1796
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} prominent reload`;
1797
private static readonly DisabledClass = `${this.EnabledClass} disabled`;
1798
1799
updateWhenCounterExtensionChanges: boolean = true;
1800
1801
constructor(
1802
@IHostService private readonly hostService: IHostService,
1803
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
1804
@IUpdateService private readonly updateService: IUpdateService,
1805
@IExtensionService private readonly extensionService: IExtensionService,
1806
@IProductService private readonly productService: IProductService,
1807
@ITelemetryService private readonly telemetryService: ITelemetryService,
1808
) {
1809
super('extensions.runtimeState', '', ExtensionRuntimeStateAction.DisabledClass, false);
1810
this._register(this.extensionService.onDidChangeExtensions(() => this.update()));
1811
this.update();
1812
}
1813
1814
update(): void {
1815
this.enabled = false;
1816
this.tooltip = '';
1817
this.class = ExtensionRuntimeStateAction.DisabledClass;
1818
1819
if (!this.extension) {
1820
return;
1821
}
1822
1823
const state = this.extension.state;
1824
if (state === ExtensionState.Installing || state === ExtensionState.Uninstalling) {
1825
return;
1826
}
1827
1828
if (this.extension.local && this.extension.local.manifest && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.localizations && this.extension.local.manifest.contributes.localizations.length > 0) {
1829
return;
1830
}
1831
1832
const runtimeState = this.extension.runtimeState;
1833
if (!runtimeState) {
1834
return;
1835
}
1836
1837
this.enabled = true;
1838
this.class = ExtensionRuntimeStateAction.EnabledClass;
1839
this.tooltip = runtimeState.reason;
1840
this.label = runtimeState.action === ExtensionRuntimeActionType.ReloadWindow ? localize('reload window', 'Reload Window')
1841
: runtimeState.action === ExtensionRuntimeActionType.RestartExtensions ? localize('restart extensions', 'Restart Extensions')
1842
: runtimeState.action === ExtensionRuntimeActionType.QuitAndInstall ? localize('restart product', 'Restart to Update')
1843
: runtimeState.action === ExtensionRuntimeActionType.ApplyUpdate || runtimeState.action === ExtensionRuntimeActionType.DownloadUpdate ? localize('update product', 'Update {0}', this.productService.nameShort) : '';
1844
}
1845
1846
override async run(): Promise<any> {
1847
const runtimeState = this.extension?.runtimeState;
1848
if (!runtimeState?.action) {
1849
return;
1850
}
1851
1852
type ExtensionRuntimeStateActionClassification = {
1853
owner: 'sandy081';
1854
comment: 'Extension runtime state action event';
1855
action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Executed action' };
1856
};
1857
type ExtensionRuntimeStateActionEvent = {
1858
action: string;
1859
};
1860
this.telemetryService.publicLog2<ExtensionRuntimeStateActionEvent, ExtensionRuntimeStateActionClassification>('extensions:runtimestate:action', {
1861
action: runtimeState.action
1862
});
1863
1864
if (runtimeState?.action === ExtensionRuntimeActionType.ReloadWindow) {
1865
return this.hostService.reload();
1866
}
1867
1868
else if (runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions) {
1869
return this.extensionsWorkbenchService.updateRunningExtensions();
1870
}
1871
1872
else if (runtimeState?.action === ExtensionRuntimeActionType.DownloadUpdate) {
1873
return this.updateService.downloadUpdate(true);
1874
}
1875
1876
else if (runtimeState?.action === ExtensionRuntimeActionType.ApplyUpdate) {
1877
return this.updateService.applyUpdate();
1878
}
1879
1880
else if (runtimeState?.action === ExtensionRuntimeActionType.QuitAndInstall) {
1881
return this.updateService.quitAndInstall();
1882
}
1883
1884
}
1885
}
1886
1887
function isThemeFromExtension(theme: IWorkbenchTheme, extension: IExtension | undefined | null): boolean {
1888
return !!(extension && theme.extensionData && ExtensionIdentifier.equals(theme.extensionData.extensionId, extension.identifier.id));
1889
}
1890
1891
function getQuickPickEntries(themes: IWorkbenchTheme[], currentTheme: IWorkbenchTheme, extension: IExtension | null | undefined, showCurrentTheme: boolean): QuickPickItem[] {
1892
const picks: QuickPickItem[] = [];
1893
for (const theme of themes) {
1894
if (isThemeFromExtension(theme, extension) && !(showCurrentTheme && theme === currentTheme)) {
1895
picks.push({ label: theme.label, id: theme.id });
1896
}
1897
}
1898
if (showCurrentTheme) {
1899
picks.push({ type: 'separator', label: localize('current', "current") });
1900
picks.push({ label: currentTheme.label, id: currentTheme.id });
1901
}
1902
return picks;
1903
}
1904
1905
export class SetColorThemeAction extends ExtensionAction {
1906
1907
static readonly ID = 'workbench.extensions.action.setColorTheme';
1908
static readonly TITLE = localize2('workbench.extensions.action.setColorTheme', 'Set Color Theme');
1909
1910
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
1911
private static readonly DisabledClass = `${this.EnabledClass} disabled`;
1912
1913
constructor(
1914
@IExtensionService extensionService: IExtensionService,
1915
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
1916
@IQuickInputService private readonly quickInputService: IQuickInputService,
1917
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
1918
) {
1919
super(SetColorThemeAction.ID, SetColorThemeAction.TITLE.value, SetColorThemeAction.DisabledClass, false);
1920
this._register(Event.any<any>(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this));
1921
this.update();
1922
}
1923
1924
update(): void {
1925
this.workbenchThemeService.getColorThemes().then(colorThemes => {
1926
this.enabled = this.computeEnablement(colorThemes);
1927
this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass;
1928
});
1929
}
1930
1931
private computeEnablement(colorThemes: IWorkbenchColorTheme[]): boolean {
1932
return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemes.some(th => isThemeFromExtension(th, this.extension));
1933
}
1934
1935
override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise<any> {
1936
const colorThemes = await this.workbenchThemeService.getColorThemes();
1937
1938
if (!this.computeEnablement(colorThemes)) {
1939
return;
1940
}
1941
const currentTheme = this.workbenchThemeService.getColorTheme();
1942
1943
const delayer = new Delayer<any>(100);
1944
const picks = getQuickPickEntries(colorThemes, currentTheme, this.extension, showCurrentTheme);
1945
const pickedTheme = await this.quickInputService.pick(
1946
picks,
1947
{
1948
placeHolder: localize('select color theme', "Select Color Theme"),
1949
onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setColorTheme(item.id, undefined)),
1950
ignoreFocusLost
1951
});
1952
return this.workbenchThemeService.setColorTheme(pickedTheme ? pickedTheme.id : currentTheme.id, 'auto');
1953
}
1954
}
1955
1956
export class SetFileIconThemeAction extends ExtensionAction {
1957
1958
static readonly ID = 'workbench.extensions.action.setFileIconTheme';
1959
static readonly TITLE = localize2('workbench.extensions.action.setFileIconTheme', 'Set File Icon Theme');
1960
1961
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
1962
private static readonly DisabledClass = `${this.EnabledClass} disabled`;
1963
1964
constructor(
1965
@IExtensionService extensionService: IExtensionService,
1966
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
1967
@IQuickInputService private readonly quickInputService: IQuickInputService,
1968
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
1969
) {
1970
super(SetFileIconThemeAction.ID, SetFileIconThemeAction.TITLE.value, SetFileIconThemeAction.DisabledClass, false);
1971
this._register(Event.any<any>(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this));
1972
this.update();
1973
}
1974
1975
update(): void {
1976
this.workbenchThemeService.getFileIconThemes().then(fileIconThemes => {
1977
this.enabled = this.computeEnablement(fileIconThemes);
1978
this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass;
1979
});
1980
}
1981
1982
private computeEnablement(colorThemfileIconThemess: IWorkbenchFileIconTheme[]): boolean {
1983
return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemfileIconThemess.some(th => isThemeFromExtension(th, this.extension));
1984
}
1985
1986
override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise<any> {
1987
const fileIconThemes = await this.workbenchThemeService.getFileIconThemes();
1988
if (!this.computeEnablement(fileIconThemes)) {
1989
return;
1990
}
1991
const currentTheme = this.workbenchThemeService.getFileIconTheme();
1992
1993
const delayer = new Delayer<any>(100);
1994
const picks = getQuickPickEntries(fileIconThemes, currentTheme, this.extension, showCurrentTheme);
1995
const pickedTheme = await this.quickInputService.pick(
1996
picks,
1997
{
1998
placeHolder: localize('select file icon theme', "Select File Icon Theme"),
1999
onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setFileIconTheme(item.id, undefined)),
2000
ignoreFocusLost
2001
});
2002
return this.workbenchThemeService.setFileIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, 'auto');
2003
}
2004
}
2005
2006
export class SetProductIconThemeAction extends ExtensionAction {
2007
2008
static readonly ID = 'workbench.extensions.action.setProductIconTheme';
2009
static readonly TITLE = localize2('workbench.extensions.action.setProductIconTheme', 'Set Product Icon Theme');
2010
2011
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
2012
private static readonly DisabledClass = `${this.EnabledClass} disabled`;
2013
2014
constructor(
2015
@IExtensionService extensionService: IExtensionService,
2016
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
2017
@IQuickInputService private readonly quickInputService: IQuickInputService,
2018
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
2019
) {
2020
super(SetProductIconThemeAction.ID, SetProductIconThemeAction.TITLE.value, SetProductIconThemeAction.DisabledClass, false);
2021
this._register(Event.any<any>(extensionService.onDidChangeExtensions, workbenchThemeService.onDidProductIconThemeChange)(() => this.update(), this));
2022
this.update();
2023
}
2024
2025
update(): void {
2026
this.workbenchThemeService.getProductIconThemes().then(productIconThemes => {
2027
this.enabled = this.computeEnablement(productIconThemes);
2028
this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass;
2029
});
2030
}
2031
2032
private computeEnablement(productIconThemes: IWorkbenchProductIconTheme[]): boolean {
2033
return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && productIconThemes.some(th => isThemeFromExtension(th, this.extension));
2034
}
2035
2036
override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise<any> {
2037
const productIconThemes = await this.workbenchThemeService.getProductIconThemes();
2038
if (!this.computeEnablement(productIconThemes)) {
2039
return;
2040
}
2041
2042
const currentTheme = this.workbenchThemeService.getProductIconTheme();
2043
2044
const delayer = new Delayer<any>(100);
2045
const picks = getQuickPickEntries(productIconThemes, currentTheme, this.extension, showCurrentTheme);
2046
const pickedTheme = await this.quickInputService.pick(
2047
picks,
2048
{
2049
placeHolder: localize('select product icon theme', "Select Product Icon Theme"),
2050
onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setProductIconTheme(item.id, undefined)),
2051
ignoreFocusLost
2052
});
2053
return this.workbenchThemeService.setProductIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, 'auto');
2054
}
2055
}
2056
2057
export class SetLanguageAction extends ExtensionAction {
2058
2059
static readonly ID = 'workbench.extensions.action.setDisplayLanguage';
2060
static readonly TITLE = localize2('workbench.extensions.action.setDisplayLanguage', 'Set Display Language');
2061
2062
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`;
2063
private static readonly DisabledClass = `${this.EnabledClass} disabled`;
2064
2065
constructor(
2066
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
2067
) {
2068
super(SetLanguageAction.ID, SetLanguageAction.TITLE.value, SetLanguageAction.DisabledClass, false);
2069
this.update();
2070
}
2071
2072
update(): void {
2073
this.enabled = false;
2074
this.class = SetLanguageAction.DisabledClass;
2075
if (!this.extension) {
2076
return;
2077
}
2078
if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
2079
return;
2080
}
2081
if (this.extension.gallery && language === getLocale(this.extension.gallery)) {
2082
return;
2083
}
2084
this.enabled = true;
2085
this.class = SetLanguageAction.EnabledClass;
2086
}
2087
2088
override async run(): Promise<any> {
2089
return this.extension && this.extensionsWorkbenchService.setLanguage(this.extension);
2090
}
2091
}
2092
2093
export class ClearLanguageAction extends ExtensionAction {
2094
2095
static readonly ID = 'workbench.extensions.action.clearLanguage';
2096
static readonly TITLE = localize2('workbench.extensions.action.clearLanguage', 'Clear Display Language');
2097
2098
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`;
2099
private static readonly DisabledClass = `${this.EnabledClass} disabled`;
2100
2101
constructor(
2102
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
2103
@ILocaleService private readonly localeService: ILocaleService,
2104
) {
2105
super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false);
2106
this.update();
2107
}
2108
2109
update(): void {
2110
this.enabled = false;
2111
this.class = ClearLanguageAction.DisabledClass;
2112
if (!this.extension) {
2113
return;
2114
}
2115
if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
2116
return;
2117
}
2118
if (this.extension.gallery && language !== getLocale(this.extension.gallery)) {
2119
return;
2120
}
2121
this.enabled = true;
2122
this.class = ClearLanguageAction.EnabledClass;
2123
}
2124
2125
override async run(): Promise<any> {
2126
return this.extension && this.localeService.clearLocalePreference();
2127
}
2128
}
2129
2130
export class ShowRecommendedExtensionAction extends Action {
2131
2132
static readonly ID = 'workbench.extensions.action.showRecommendedExtension';
2133
static readonly LABEL = localize('showRecommendedExtension', "Show Recommended Extension");
2134
2135
private extensionId: string;
2136
2137
constructor(
2138
extensionId: string,
2139
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
2140
) {
2141
super(ShowRecommendedExtensionAction.ID, ShowRecommendedExtensionAction.LABEL, undefined, false);
2142
this.extensionId = extensionId;
2143
}
2144
2145
override async run(): Promise<any> {
2146
await this.extensionWorkbenchService.openSearch(`@id:${this.extensionId}`);
2147
const [extension] = await this.extensionWorkbenchService.getExtensions([{ id: this.extensionId }], { source: 'install-recommendation' }, CancellationToken.None);
2148
if (extension) {
2149
return this.extensionWorkbenchService.open(extension);
2150
}
2151
return null;
2152
}
2153
}
2154
2155
export class InstallRecommendedExtensionAction extends Action {
2156
2157
static readonly ID = 'workbench.extensions.action.installRecommendedExtension';
2158
static readonly LABEL = localize('installRecommendedExtension', "Install Recommended Extension");
2159
2160
private extensionId: string;
2161
2162
constructor(
2163
extensionId: string,
2164
@IInstantiationService private readonly instantiationService: IInstantiationService,
2165
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
2166
) {
2167
super(InstallRecommendedExtensionAction.ID, InstallRecommendedExtensionAction.LABEL, undefined, false);
2168
this.extensionId = extensionId;
2169
}
2170
2171
override async run(): Promise<any> {
2172
await this.extensionWorkbenchService.openSearch(`@id:${this.extensionId}`);
2173
const [extension] = await this.extensionWorkbenchService.getExtensions([{ id: this.extensionId }], { source: 'install-recommendation' }, CancellationToken.None);
2174
if (extension) {
2175
await this.extensionWorkbenchService.open(extension);
2176
try {
2177
await this.extensionWorkbenchService.install(extension);
2178
} catch (err) {
2179
this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, undefined, extension.latestVersion, InstallOperation.Install, err).run();
2180
}
2181
}
2182
}
2183
}
2184
2185
export class IgnoreExtensionRecommendationAction extends Action {
2186
2187
static readonly ID = 'extensions.ignore';
2188
2189
private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} ignore`;
2190
2191
constructor(
2192
private readonly extension: IExtension,
2193
@IExtensionIgnoredRecommendationsService private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService,
2194
) {
2195
super(IgnoreExtensionRecommendationAction.ID, 'Ignore Recommendation');
2196
2197
this.class = IgnoreExtensionRecommendationAction.Class;
2198
this.tooltip = localize('ignoreExtensionRecommendation', "Do not recommend this extension again");
2199
this.enabled = true;
2200
}
2201
2202
public override run(): Promise<any> {
2203
this.extensionRecommendationsManagementService.toggleGlobalIgnoredRecommendation(this.extension.identifier.id, true);
2204
return Promise.resolve();
2205
}
2206
}
2207
2208
export class UndoIgnoreExtensionRecommendationAction extends Action {
2209
2210
static readonly ID = 'extensions.ignore';
2211
2212
private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} undo-ignore`;
2213
2214
constructor(
2215
private readonly extension: IExtension,
2216
@IExtensionIgnoredRecommendationsService private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService,
2217
) {
2218
super(UndoIgnoreExtensionRecommendationAction.ID, 'Undo');
2219
2220
this.class = UndoIgnoreExtensionRecommendationAction.Class;
2221
this.tooltip = localize('undo', "Undo");
2222
this.enabled = true;
2223
}
2224
2225
public override run(): Promise<any> {
2226
this.extensionRecommendationsManagementService.toggleGlobalIgnoredRecommendation(this.extension.identifier.id, false);
2227
return Promise.resolve();
2228
}
2229
}
2230
2231
export abstract class AbstractConfigureRecommendedExtensionsAction extends Action {
2232
2233
constructor(
2234
id: string,
2235
label: string,
2236
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
2237
@IFileService private readonly fileService: IFileService,
2238
@ITextFileService private readonly textFileService: ITextFileService,
2239
@IEditorService protected editorService: IEditorService,
2240
@IJSONEditingService private readonly jsonEditingService: IJSONEditingService,
2241
@ITextModelService private readonly textModelResolverService: ITextModelService
2242
) {
2243
super(id, label);
2244
}
2245
2246
protected openExtensionsFile(extensionsFileResource: URI): Promise<any> {
2247
return this.getOrCreateExtensionsFile(extensionsFileResource)
2248
.then(({ created, content }) =>
2249
this.getSelectionPosition(content, extensionsFileResource, ['recommendations'])
2250
.then(selection => this.editorService.openEditor({
2251
resource: extensionsFileResource,
2252
options: {
2253
pinned: created,
2254
selection
2255
}
2256
})),
2257
error => Promise.reject(new Error(localize('OpenExtensionsFile.failed', "Unable to create 'extensions.json' file inside the '.vscode' folder ({0}).", error))));
2258
}
2259
2260
protected openWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise<any> {
2261
return this.getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile)
2262
.then(content => this.getSelectionPosition(content.value.toString(), content.resource, ['extensions', 'recommendations']))
2263
.then(selection => this.editorService.openEditor({
2264
resource: workspaceConfigurationFile,
2265
options: {
2266
selection,
2267
forceReload: true // because content has changed
2268
}
2269
}));
2270
}
2271
2272
private getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise<IFileContent> {
2273
return Promise.resolve(this.fileService.readFile(workspaceConfigurationFile))
2274
.then(content => {
2275
const workspaceRecommendations = <IExtensionsConfigContent>json.parse(content.value.toString())['extensions'];
2276
if (!workspaceRecommendations || !workspaceRecommendations.recommendations) {
2277
return this.jsonEditingService.write(workspaceConfigurationFile, [{ path: ['extensions'], value: { recommendations: [] } }], true)
2278
.then(() => this.fileService.readFile(workspaceConfigurationFile));
2279
}
2280
return content;
2281
});
2282
}
2283
2284
private getSelectionPosition(content: string, resource: URI, path: json.JSONPath): Promise<ITextEditorSelection | undefined> {
2285
const tree = json.parseTree(content);
2286
const node = json.findNodeAtLocation(tree, path);
2287
if (node && node.parent && node.parent.children) {
2288
const recommendationsValueNode = node.parent.children[1];
2289
const lastExtensionNode = recommendationsValueNode.children && recommendationsValueNode.children.length ? recommendationsValueNode.children[recommendationsValueNode.children.length - 1] : null;
2290
const offset = lastExtensionNode ? lastExtensionNode.offset + lastExtensionNode.length : recommendationsValueNode.offset + 1;
2291
return Promise.resolve(this.textModelResolverService.createModelReference(resource))
2292
.then(reference => {
2293
const position = reference.object.textEditorModel.getPositionAt(offset);
2294
reference.dispose();
2295
return {
2296
startLineNumber: position.lineNumber,
2297
startColumn: position.column,
2298
endLineNumber: position.lineNumber,
2299
endColumn: position.column,
2300
};
2301
});
2302
}
2303
return Promise.resolve(undefined);
2304
}
2305
2306
private getOrCreateExtensionsFile(extensionsFileResource: URI): Promise<{ created: boolean; extensionsFileResource: URI; content: string }> {
2307
return Promise.resolve(this.fileService.readFile(extensionsFileResource)).then(content => {
2308
return { created: false, extensionsFileResource, content: content.value.toString() };
2309
}, err => {
2310
return this.textFileService.write(extensionsFileResource, ExtensionsConfigurationInitialContent).then(() => {
2311
return { created: true, extensionsFileResource, content: ExtensionsConfigurationInitialContent };
2312
});
2313
});
2314
}
2315
}
2316
2317
export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction {
2318
2319
static readonly ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions';
2320
static readonly LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)");
2321
2322
constructor(
2323
id: string,
2324
label: string,
2325
@IFileService fileService: IFileService,
2326
@ITextFileService textFileService: ITextFileService,
2327
@IWorkspaceContextService contextService: IWorkspaceContextService,
2328
@IEditorService editorService: IEditorService,
2329
@IJSONEditingService jsonEditingService: IJSONEditingService,
2330
@ITextModelService textModelResolverService: ITextModelService
2331
) {
2332
super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService);
2333
this._register(this.contextService.onDidChangeWorkbenchState(() => this.update(), this));
2334
this.update();
2335
}
2336
2337
private update(): void {
2338
this.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY;
2339
}
2340
2341
public override run(): Promise<void> {
2342
switch (this.contextService.getWorkbenchState()) {
2343
case WorkbenchState.FOLDER:
2344
return this.openExtensionsFile(this.contextService.getWorkspace().folders[0].toResource(EXTENSIONS_CONFIG));
2345
case WorkbenchState.WORKSPACE:
2346
return this.openWorkspaceConfigurationFile(this.contextService.getWorkspace().configuration!);
2347
}
2348
return Promise.resolve();
2349
}
2350
}
2351
2352
export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction {
2353
2354
static readonly ID = 'workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions';
2355
static readonly LABEL = localize('configureWorkspaceFolderRecommendedExtensions', "Configure Recommended Extensions (Workspace Folder)");
2356
2357
constructor(
2358
id: string,
2359
label: string,
2360
@IFileService fileService: IFileService,
2361
@ITextFileService textFileService: ITextFileService,
2362
@IWorkspaceContextService contextService: IWorkspaceContextService,
2363
@IEditorService editorService: IEditorService,
2364
@IJSONEditingService jsonEditingService: IJSONEditingService,
2365
@ITextModelService textModelResolverService: ITextModelService,
2366
@ICommandService private readonly commandService: ICommandService
2367
) {
2368
super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService);
2369
}
2370
2371
public override run(): Promise<any> {
2372
const folderCount = this.contextService.getWorkspace().folders.length;
2373
const pickFolderPromise = folderCount === 1 ? Promise.resolve(this.contextService.getWorkspace().folders[0]) : this.commandService.executeCommand<IWorkspaceFolder>(PICK_WORKSPACE_FOLDER_COMMAND_ID);
2374
return Promise.resolve(pickFolderPromise)
2375
.then(workspaceFolder => {
2376
if (workspaceFolder) {
2377
return this.openExtensionsFile(workspaceFolder.toResource(EXTENSIONS_CONFIG));
2378
}
2379
return null;
2380
});
2381
}
2382
}
2383
2384
export class ExtensionStatusLabelAction extends Action implements IExtensionContainer {
2385
2386
private static readonly ENABLED_CLASS = `${ExtensionAction.TEXT_ACTION_CLASS} extension-status-label`;
2387
private static readonly DISABLED_CLASS = `${this.ENABLED_CLASS} hide`;
2388
2389
private initialStatus: ExtensionState | null = null;
2390
private status: ExtensionState | null = null;
2391
private version: string | null = null;
2392
private enablementState: EnablementState | null = null;
2393
2394
private _extension: IExtension | null = null;
2395
get extension(): IExtension | null { return this._extension; }
2396
set extension(extension: IExtension | null) {
2397
if (!(this._extension && extension && areSameExtensions(this._extension.identifier, extension.identifier))) {
2398
// Different extension. Reset
2399
this.initialStatus = null;
2400
this.status = null;
2401
this.enablementState = null;
2402
}
2403
this._extension = extension;
2404
this.update();
2405
}
2406
2407
constructor(
2408
@IExtensionService private readonly extensionService: IExtensionService,
2409
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
2410
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService
2411
) {
2412
super('extensions.action.statusLabel', '', ExtensionStatusLabelAction.DISABLED_CLASS, false);
2413
}
2414
2415
update(): void {
2416
const label = this.computeLabel();
2417
this.label = label || '';
2418
this.class = label ? ExtensionStatusLabelAction.ENABLED_CLASS : ExtensionStatusLabelAction.DISABLED_CLASS;
2419
}
2420
2421
private computeLabel(): string | null {
2422
if (!this.extension) {
2423
return null;
2424
}
2425
2426
const currentStatus = this.status;
2427
const currentVersion = this.version;
2428
const currentEnablementState = this.enablementState;
2429
this.status = this.extension.state;
2430
this.version = this.extension.version;
2431
if (this.initialStatus === null) {
2432
this.initialStatus = this.status;
2433
}
2434
this.enablementState = this.extension.enablementState;
2435
2436
const canAddExtension = () => {
2437
const runningExtension = this.extensionService.extensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0];
2438
if (this.extension!.local) {
2439
if (runningExtension && this.extension!.version === runningExtension.version) {
2440
return true;
2441
}
2442
return this.extensionService.canAddExtension(toExtensionDescription(this.extension!.local));
2443
}
2444
return false;
2445
};
2446
const canRemoveExtension = () => {
2447
if (this.extension!.local) {
2448
if (this.extensionService.extensions.every(e => !(areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.extension!.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(e))))) {
2449
return true;
2450
}
2451
return this.extensionService.canRemoveExtension(toExtensionDescription(this.extension!.local));
2452
}
2453
return false;
2454
};
2455
2456
if (currentStatus !== null) {
2457
if (currentStatus === ExtensionState.Installing && this.status === ExtensionState.Installed) {
2458
if (this.initialStatus === ExtensionState.Uninstalled && canAddExtension()) {
2459
return localize('installed', "Installed");
2460
}
2461
if (this.initialStatus === ExtensionState.Installed && this.version !== currentVersion && canAddExtension()) {
2462
return localize('updated', "Updated");
2463
}
2464
return null;
2465
}
2466
if (currentStatus === ExtensionState.Uninstalling && this.status === ExtensionState.Uninstalled) {
2467
this.initialStatus = this.status;
2468
return canRemoveExtension() ? localize('uninstalled', "Uninstalled") : null;
2469
}
2470
}
2471
2472
if (currentEnablementState !== null) {
2473
const currentlyEnabled = this.extensionEnablementService.isEnabledEnablementState(currentEnablementState);
2474
const enabled = this.extensionEnablementService.isEnabledEnablementState(this.enablementState);
2475
if (!currentlyEnabled && enabled) {
2476
return canAddExtension() ? localize('enabled', "Enabled") : null;
2477
}
2478
if (currentlyEnabled && !enabled) {
2479
return canRemoveExtension() ? localize('disabled', "Disabled") : null;
2480
}
2481
2482
}
2483
2484
return null;
2485
}
2486
2487
override run(): Promise<any> {
2488
return Promise.resolve();
2489
}
2490
2491
}
2492
2493
export class ToggleSyncExtensionAction extends DropDownExtensionAction {
2494
2495
private static readonly IGNORED_SYNC_CLASS = `${ExtensionAction.ICON_ACTION_CLASS} extension-sync ${ThemeIcon.asClassName(syncIgnoredIcon)}`;
2496
private static readonly SYNC_CLASS = `${this.ICON_ACTION_CLASS} extension-sync ${ThemeIcon.asClassName(syncEnabledIcon)}`;
2497
2498
constructor(
2499
@IConfigurationService private readonly configurationService: IConfigurationService,
2500
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
2501
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
2502
@IInstantiationService instantiationService: IInstantiationService,
2503
) {
2504
super('extensions.sync', '', ToggleSyncExtensionAction.SYNC_CLASS, false, instantiationService);
2505
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('settingsSync.ignoredExtensions'))(() => this.update()));
2506
this._register(userDataSyncEnablementService.onDidChangeEnablement(() => this.update()));
2507
this.update();
2508
}
2509
2510
update(): void {
2511
this.enabled = !!this.extension && this.userDataSyncEnablementService.isEnabled() && this.extension.state === ExtensionState.Installed;
2512
if (this.extension) {
2513
const isIgnored = this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension);
2514
this.class = isIgnored ? ToggleSyncExtensionAction.IGNORED_SYNC_CLASS : ToggleSyncExtensionAction.SYNC_CLASS;
2515
this.tooltip = isIgnored ? localize('ignored', "This extension is ignored during sync") : localize('synced', "This extension is synced");
2516
}
2517
}
2518
2519
override async run(): Promise<any> {
2520
return super.run([
2521
[
2522
new Action(
2523
'extensions.syncignore',
2524
this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension!) ? localize('sync', "Sync this extension") : localize('do not sync', "Do not sync this extension")
2525
, undefined, true, () => this.extensionsWorkbenchService.toggleExtensionIgnoredToSync(this.extension!))
2526
]
2527
]);
2528
}
2529
}
2530
2531
export type ExtensionStatus = { readonly message: IMarkdownString; readonly icon?: ThemeIcon };
2532
2533
export class ExtensionStatusAction extends ExtensionAction {
2534
2535
private static readonly CLASS = `${ExtensionAction.ICON_ACTION_CLASS} extension-status`;
2536
2537
updateWhenCounterExtensionChanges: boolean = true;
2538
2539
private _status: ExtensionStatus[] = [];
2540
get status(): ExtensionStatus[] { return this._status; }
2541
2542
private readonly _onDidChangeStatus = this._register(new Emitter<void>());
2543
readonly onDidChangeStatus = this._onDidChangeStatus.event;
2544
2545
private readonly updateThrottler = this._register(new Throttler());
2546
2547
constructor(
2548
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
2549
@ILabelService private readonly labelService: ILabelService,
2550
@ICommandService private readonly commandService: ICommandService,
2551
@IWorkspaceTrustEnablementService private readonly workspaceTrustEnablementService: IWorkspaceTrustEnablementService,
2552
@IWorkspaceTrustManagementService private readonly workspaceTrustService: IWorkspaceTrustManagementService,
2553
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
2554
@IExtensionService private readonly extensionService: IExtensionService,
2555
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
2556
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
2557
@IProductService private readonly productService: IProductService,
2558
@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,
2559
@IWorkbenchExtensionEnablementService private readonly workbenchExtensionEnablementService: IWorkbenchExtensionEnablementService,
2560
@IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService,
2561
@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService,
2562
) {
2563
super('extensions.status', '', `${ExtensionStatusAction.CLASS} hide`, false);
2564
this._register(this.labelService.onDidChangeFormatters(() => this.update(), this));
2565
this._register(this.extensionService.onDidChangeExtensions(() => this.update()));
2566
this._register(this.extensionFeaturesManagementService.onDidChangeAccessData(() => this.update()));
2567
this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this.update()));
2568
this.update();
2569
}
2570
2571
update(): void {
2572
this.updateThrottler.queue(() => this.computeAndUpdateStatus());
2573
}
2574
2575
private async computeAndUpdateStatus(): Promise<void> {
2576
this.updateStatus(undefined, true);
2577
this.enabled = false;
2578
2579
if (!this.extension) {
2580
return;
2581
}
2582
2583
if (this.extension.isMalicious) {
2584
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('malicious tooltip', "This extension was reported to be problematic.")) }, true);
2585
return;
2586
}
2587
2588
if (this.extension.state === ExtensionState.Uninstalled && this.extension.gallery && !this.extension.gallery.isSigned && shouldRequireRepositorySignatureFor(this.extension.private, await this.extensionGalleryManifestService.getExtensionGalleryManifest())) {
2589
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('not signed tooltip', "This extension is not signed by the Extension Marketplace.")) }, true);
2590
return;
2591
}
2592
2593
if (this.extension.deprecationInfo) {
2594
if (this.extension.deprecationInfo.extension) {
2595
const link = `[${this.extension.deprecationInfo.extension.displayName}](${createCommandUri('extension.open', this.extension.deprecationInfo.extension.id)})`;
2596
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate extension tooltip', "This extension is deprecated. Use the {0} extension instead.", link)) }, true);
2597
} else if (this.extension.deprecationInfo.settings) {
2598
const link = `[${localize('settings', "settings")}](${createCommandUri('workbench.action.openSettings', this.extension.deprecationInfo.settings.map(setting => `@id:${setting}`).join(' '))}})`;
2599
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated as this functionality is now built-in to VS Code. Configure these {0} to use this functionality.", link)) }, true);
2600
} else {
2601
const message = new MarkdownString(localize('deprecated tooltip', "This extension is deprecated as it is no longer being maintained."));
2602
if (this.extension.deprecationInfo.additionalInfo) {
2603
message.appendMarkdown(` ${this.extension.deprecationInfo.additionalInfo}`);
2604
}
2605
this.updateStatus({ icon: warningIcon, message }, true);
2606
}
2607
return;
2608
}
2609
2610
if (this.extension.missingFromGallery) {
2611
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('missing from gallery tooltip', "This extension is no longer available on the Extension Marketplace.")) }, true);
2612
return;
2613
}
2614
2615
if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
2616
return;
2617
}
2618
2619
if (this.extension.outdated) {
2620
const message = await this.extensionsWorkbenchService.shouldRequireConsentToUpdate(this.extension);
2621
if (message) {
2622
const markdown = new MarkdownString();
2623
markdown.appendMarkdown(`${message} `);
2624
markdown.appendMarkdown(
2625
localize('auto update message', "Please [review the extension]({0}) and update it manually.",
2626
this.extension.hasChangelog()
2627
? createCommandUri('extension.open', this.extension.identifier.id, ExtensionEditorTab.Changelog).toString()
2628
: this.extension.repository
2629
? this.extension.repository
2630
: createCommandUri('extension.open', this.extension.identifier.id).toString()
2631
));
2632
this.updateStatus({ icon: warningIcon, message: markdown }, true);
2633
}
2634
}
2635
2636
if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled) {
2637
const result = await this.extensionsWorkbenchService.canInstall(this.extension);
2638
if (result !== true) {
2639
this.updateStatus({ icon: warningIcon, message: result }, true);
2640
return;
2641
}
2642
}
2643
2644
if (!this.extension.local ||
2645
!this.extension.server ||
2646
this.extension.state !== ExtensionState.Installed
2647
) {
2648
return;
2649
}
2650
2651
// Extension is disabled by allowed list
2652
if (this.extension.enablementState === EnablementState.DisabledByAllowlist) {
2653
const result = this.allowedExtensionsService.isAllowed(this.extension.local);
2654
if (result !== true) {
2655
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('disabled - not allowed', "This extension is disabled because {0}", result.value)) }, true);
2656
return;
2657
}
2658
}
2659
2660
// Extension is disabled by environment
2661
if (this.extension.enablementState === EnablementState.DisabledByEnvironment) {
2662
this.updateStatus({ message: new MarkdownString(localize('disabled by environment', "This extension is disabled by the environment.")) }, true);
2663
return;
2664
}
2665
2666
// Extension is enabled by environment
2667
if (this.extension.enablementState === EnablementState.EnabledByEnvironment) {
2668
this.updateStatus({ message: new MarkdownString(localize('enabled by environment', "This extension is enabled because it is required in the current environment.")) }, true);
2669
return;
2670
}
2671
2672
// Extension is disabled by virtual workspace
2673
if (this.extension.enablementState === EnablementState.DisabledByVirtualWorkspace) {
2674
const details = getWorkspaceSupportTypeMessage(this.extension.local.manifest.capabilities?.virtualWorkspaces);
2675
this.updateStatus({ icon: infoIcon, message: new MarkdownString(details ? escapeMarkdownSyntaxTokens(details) : localize('disabled because of virtual workspace', "This extension has been disabled because it does not support virtual workspaces.")) }, true);
2676
return;
2677
}
2678
2679
// Limited support in Virtual Workspace
2680
if (isVirtualWorkspace(this.contextService.getWorkspace())) {
2681
const virtualSupportType = this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(this.extension.local.manifest);
2682
const details = getWorkspaceSupportTypeMessage(this.extension.local.manifest.capabilities?.virtualWorkspaces);
2683
if (virtualSupportType === 'limited' || details) {
2684
this.updateStatus({ icon: warningIcon, message: new MarkdownString(details ? escapeMarkdownSyntaxTokens(details) : localize('extension limited because of virtual workspace', "This extension has limited features because the current workspace is virtual.")) }, true);
2685
return;
2686
}
2687
}
2688
2689
// Unification
2690
if (this.extension.enablementState === EnablementState.DisabledByUnification) {
2691
this.updateStatus({ icon: infoIcon, message: new MarkdownString(localize('extension disabled because of unification', "All GitHub Copilot functionality is now being served from the GitHub Copilot Chat extension. To temporarily opt out of this extension unification, toggle the {0} setting.", '`chat.extensionUnification.enabled`')) }, true);
2692
return;
2693
}
2694
2695
if (!this.workspaceTrustService.isWorkspaceTrusted() &&
2696
// Extension is disabled by untrusted workspace
2697
(this.extension.enablementState === EnablementState.DisabledByTrustRequirement ||
2698
// All disabled dependencies of the extension are disabled by untrusted workspace
2699
(this.extension.enablementState === EnablementState.DisabledByExtensionDependency && this.workbenchExtensionEnablementService.getDependenciesEnablementStates(this.extension.local).every(([, enablementState]) => this.workbenchExtensionEnablementService.isEnabledEnablementState(enablementState) || enablementState === EnablementState.DisabledByTrustRequirement)))) {
2700
this.enabled = true;
2701
const untrustedDetails = getWorkspaceSupportTypeMessage(this.extension.local.manifest.capabilities?.untrustedWorkspaces);
2702
this.updateStatus({ icon: trustIcon, message: new MarkdownString(untrustedDetails ? escapeMarkdownSyntaxTokens(untrustedDetails) : localize('extension disabled because of trust requirement', "This extension has been disabled because the current workspace is not trusted.")) }, true);
2703
return;
2704
}
2705
2706
// Limited support in Untrusted Workspace
2707
if (this.workspaceTrustEnablementService.isWorkspaceTrustEnabled() && !this.workspaceTrustService.isWorkspaceTrusted()) {
2708
const untrustedSupportType = this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(this.extension.local.manifest);
2709
const untrustedDetails = getWorkspaceSupportTypeMessage(this.extension.local.manifest.capabilities?.untrustedWorkspaces);
2710
if (untrustedSupportType === 'limited' || untrustedDetails) {
2711
this.enabled = true;
2712
this.updateStatus({ icon: trustIcon, message: new MarkdownString(untrustedDetails ? escapeMarkdownSyntaxTokens(untrustedDetails) : localize('extension limited because of trust requirement', "This extension has limited features because the current workspace is not trusted.")) }, true);
2713
return;
2714
}
2715
}
2716
2717
// Extension is disabled by extension kind
2718
if (this.extension.enablementState === EnablementState.DisabledByExtensionKind) {
2719
if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) {
2720
let message;
2721
// Extension on Local Server
2722
if (this.extensionManagementServerService.localExtensionManagementServer === this.extension.server) {
2723
if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local.manifest)) {
2724
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
2725
message = new MarkdownString(`${localize('Install in remote server to enable', "This extension is disabled in this workspace because it is defined to run in the Remote Extension Host. Please install the extension in '{0}' to enable.", this.extensionManagementServerService.remoteExtensionManagementServer.label)} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`);
2726
}
2727
}
2728
}
2729
// Extension on Remote Server
2730
else if (this.extensionManagementServerService.remoteExtensionManagementServer === this.extension.server) {
2731
if (this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local.manifest)) {
2732
if (this.extensionManagementServerService.localExtensionManagementServer) {
2733
message = new MarkdownString(`${localize('Install in local server to enable', "This extension is disabled in this workspace because it is defined to run in the Local Extension Host. Please install the extension locally to enable.", this.extensionManagementServerService.remoteExtensionManagementServer.label)} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`);
2734
} else if (isWeb) {
2735
message = new MarkdownString(`${localize('Defined to run in desktop', "This extension is disabled because it is defined to run only in {0} for the Desktop.", this.productService.nameLong)} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`);
2736
}
2737
}
2738
}
2739
// Extension on Web Server
2740
else if (this.extensionManagementServerService.webExtensionManagementServer === this.extension.server) {
2741
message = new MarkdownString(`${localize('Cannot be enabled', "This extension is disabled because it is not supported in {0} for the Web.", this.productService.nameLong)} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`);
2742
}
2743
if (message) {
2744
this.updateStatus({ icon: warningIcon, message }, true);
2745
}
2746
return;
2747
}
2748
}
2749
2750
const extensionId = new ExtensionIdentifier(this.extension.identifier.id);
2751
const features = Registry.as<IExtensionFeaturesRegistry>(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures();
2752
for (const feature of features) {
2753
const status = this.extensionFeaturesManagementService.getAccessData(extensionId, feature.id)?.current?.status;
2754
const manageAccessLink = `[${localize('manage access', 'Manage Access')}](${createCommandUri('extension.open', this.extension.identifier.id, ExtensionEditorTab.Features, false, feature.id)})`;
2755
if (status?.severity === Severity.Error) {
2756
this.updateStatus({ icon: errorIcon, message: new MarkdownString().appendText(status.message).appendMarkdown(` ${manageAccessLink}`) }, true);
2757
return;
2758
}
2759
if (status?.severity === Severity.Warning) {
2760
this.updateStatus({ icon: warningIcon, message: new MarkdownString().appendText(status.message).appendMarkdown(` ${manageAccessLink}`) }, true);
2761
return;
2762
}
2763
}
2764
2765
// Remote Workspace
2766
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
2767
if (isLanguagePackExtension(this.extension.local.manifest)) {
2768
if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) {
2769
const message = this.extension.server === this.extensionManagementServerService.localExtensionManagementServer
2770
? new MarkdownString(localize('Install language pack also in remote server', "Install the language pack extension on '{0}' to enable it there also.", this.extensionManagementServerService.remoteExtensionManagementServer.label))
2771
: new MarkdownString(localize('Install language pack also locally', "Install the language pack extension locally to enable it there also."));
2772
this.updateStatus({ icon: infoIcon, message }, true);
2773
}
2774
return;
2775
}
2776
2777
const runningExtension = this.extensionService.extensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0];
2778
const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)) : null;
2779
if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) {
2780
if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local.manifest)) {
2781
this.updateStatus({ icon: infoIcon, message: new MarkdownString(`${localize('enabled remotely', "This extension is enabled in the Remote Extension Host because it prefers to run there.")} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`) }, true);
2782
}
2783
return;
2784
}
2785
2786
if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) {
2787
if (this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local.manifest)) {
2788
this.updateStatus({ icon: infoIcon, message: new MarkdownString(`${localize('enabled locally', "This extension is enabled in the Local Extension Host because it prefers to run there.")} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`) }, true);
2789
}
2790
return;
2791
}
2792
2793
if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.webExtensionManagementServer) {
2794
if (this.extensionManifestPropertiesService.canExecuteOnWeb(this.extension.local.manifest)) {
2795
this.updateStatus({ icon: infoIcon, message: new MarkdownString(`${localize('enabled in web worker', "This extension is enabled in the Web Worker Extension Host because it prefers to run there.")} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`) }, true);
2796
}
2797
return;
2798
}
2799
}
2800
2801
// Extension is disabled by its dependency
2802
if (this.extension.enablementState === EnablementState.DisabledByExtensionDependency) {
2803
this.updateStatus({
2804
icon: warningIcon,
2805
message: new MarkdownString(localize('extension disabled because of dependency', "This extension depends on an extension that is disabled."))
2806
.appendMarkdown(`&nbsp;[${localize('dependencies', "Show Dependencies")}](${createCommandUri('extension.open', this.extension.identifier.id, ExtensionEditorTab.Dependencies)})`)
2807
}, true);
2808
return;
2809
}
2810
2811
if (!this.extension.local.isValid) {
2812
const errors = this.extension.local.validations.filter(([severity]) => severity === Severity.Error).map(([, message]) => message);
2813
this.updateStatus({ icon: warningIcon, message: new MarkdownString(errors.join(' ').trim()) }, true);
2814
return;
2815
}
2816
2817
const isEnabled = this.workbenchExtensionEnablementService.isEnabled(this.extension.local);
2818
const isRunning = this.extensionService.extensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier));
2819
2820
if (!this.extension.isWorkspaceScoped && isEnabled && isRunning) {
2821
if (this.extension.enablementState === EnablementState.EnabledWorkspace) {
2822
this.updateStatus({ message: new MarkdownString(localize('workspace enabled', "This extension is enabled for this workspace by the user.")) }, true);
2823
return;
2824
}
2825
if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
2826
if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) {
2827
this.updateStatus({ message: new MarkdownString(localize('extension enabled on remote', "Extension is enabled on '{0}'", this.extension.server.label)) }, true);
2828
return;
2829
}
2830
}
2831
if (this.extension.enablementState === EnablementState.EnabledGlobally) {
2832
return;
2833
}
2834
}
2835
2836
if (!isEnabled && !isRunning) {
2837
if (this.extension.enablementState === EnablementState.DisabledGlobally) {
2838
this.updateStatus({ message: new MarkdownString(localize('globally disabled', "This extension is disabled globally by the user.")) }, true);
2839
return;
2840
}
2841
if (this.extension.enablementState === EnablementState.DisabledWorkspace) {
2842
this.updateStatus({ message: new MarkdownString(localize('workspace disabled', "This extension is disabled for this workspace by the user.")) }, true);
2843
return;
2844
}
2845
}
2846
}
2847
2848
private updateStatus(status: ExtensionStatus | undefined, updateClass: boolean): void {
2849
if (status) {
2850
if (this._status.some(s => s.message.value === status.message.value && s.icon?.id === status.icon?.id)) {
2851
return;
2852
}
2853
} else {
2854
if (this._status.length === 0) {
2855
return;
2856
}
2857
this._status = [];
2858
}
2859
2860
if (status) {
2861
this._status.push(status);
2862
this._status.sort((a, b) =>
2863
b.icon === trustIcon ? -1 :
2864
a.icon === trustIcon ? 1 :
2865
b.icon === errorIcon ? -1 :
2866
a.icon === errorIcon ? 1 :
2867
b.icon === warningIcon ? -1 :
2868
a.icon === warningIcon ? 1 :
2869
b.icon === infoIcon ? -1 :
2870
a.icon === infoIcon ? 1 :
2871
0
2872
);
2873
}
2874
2875
if (updateClass) {
2876
if (status?.icon === errorIcon) {
2877
this.class = `${ExtensionStatusAction.CLASS} extension-status-error ${ThemeIcon.asClassName(errorIcon)}`;
2878
}
2879
else if (status?.icon === warningIcon) {
2880
this.class = `${ExtensionStatusAction.CLASS} extension-status-warning ${ThemeIcon.asClassName(warningIcon)}`;
2881
}
2882
else if (status?.icon === infoIcon) {
2883
this.class = `${ExtensionStatusAction.CLASS} extension-status-info ${ThemeIcon.asClassName(infoIcon)}`;
2884
}
2885
else if (status?.icon === trustIcon) {
2886
this.class = `${ExtensionStatusAction.CLASS} ${ThemeIcon.asClassName(trustIcon)}`;
2887
}
2888
else {
2889
this.class = `${ExtensionStatusAction.CLASS} hide`;
2890
}
2891
}
2892
this._onDidChangeStatus.fire();
2893
}
2894
2895
override async run(): Promise<any> {
2896
if (this._status[0]?.icon === trustIcon) {
2897
return this.commandService.executeCommand('workbench.trust.manage');
2898
}
2899
}
2900
}
2901
2902
export class InstallSpecificVersionOfExtensionAction extends Action {
2903
2904
static readonly ID = 'workbench.extensions.action.install.specificVersion';
2905
static readonly LABEL = localize('install previous version', "Install Specific Version of Extension...");
2906
2907
constructor(
2908
id: string = InstallSpecificVersionOfExtensionAction.ID, label: string = InstallSpecificVersionOfExtensionAction.LABEL,
2909
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
2910
@IQuickInputService private readonly quickInputService: IQuickInputService,
2911
@IInstantiationService private readonly instantiationService: IInstantiationService,
2912
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
2913
) {
2914
super(id, label);
2915
}
2916
2917
override get enabled(): boolean {
2918
return this.extensionsWorkbenchService.local.some(l => this.isEnabled(l));
2919
}
2920
2921
override async run(): Promise<any> {
2922
const extensionPick = await this.quickInputService.pick(this.getExtensionEntries(), { placeHolder: localize('selectExtension', "Select Extension"), matchOnDetail: true });
2923
if (extensionPick && extensionPick.extension) {
2924
const action = this.instantiationService.createInstance(InstallAnotherVersionAction, extensionPick.extension, true);
2925
await action.run();
2926
await this.extensionsWorkbenchService.openSearch(extensionPick.extension.identifier.id);
2927
}
2928
}
2929
2930
private isEnabled(extension: IExtension): boolean {
2931
const action = this.instantiationService.createInstance(InstallAnotherVersionAction, extension, true);
2932
return action.enabled && !!extension.local && this.extensionEnablementService.isEnabled(extension.local);
2933
}
2934
2935
private async getExtensionEntries(): Promise<IExtensionPickItem[]> {
2936
const installed = await this.extensionsWorkbenchService.queryLocal();
2937
const entries: IExtensionPickItem[] = [];
2938
for (const extension of installed) {
2939
if (this.isEnabled(extension)) {
2940
entries.push({
2941
id: extension.identifier.id,
2942
label: extension.displayName || extension.identifier.id,
2943
description: extension.identifier.id,
2944
extension,
2945
});
2946
}
2947
}
2948
return entries.sort((e1, e2) => e1.extension.displayName.localeCompare(e2.extension.displayName));
2949
}
2950
}
2951
2952
interface IExtensionPickItem extends IQuickPickItem {
2953
extension: IExtension;
2954
}
2955
2956
export abstract class AbstractInstallExtensionsInServerAction extends Action {
2957
2958
private extensions: IExtension[] | undefined = undefined;
2959
2960
constructor(
2961
id: string,
2962
@IExtensionsWorkbenchService protected readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
2963
@IQuickInputService private readonly quickInputService: IQuickInputService,
2964
@INotificationService private readonly notificationService: INotificationService,
2965
@IProgressService private readonly progressService: IProgressService,
2966
) {
2967
super(id);
2968
this.update();
2969
this.extensionsWorkbenchService.queryLocal().then(() => this.updateExtensions());
2970
this._register(this.extensionsWorkbenchService.onChange(() => {
2971
if (this.extensions) {
2972
this.updateExtensions();
2973
}
2974
}));
2975
}
2976
2977
private updateExtensions(): void {
2978
this.extensions = this.extensionsWorkbenchService.local;
2979
this.update();
2980
}
2981
2982
private update(): void {
2983
this.enabled = !!this.extensions && this.getExtensionsToInstall(this.extensions).length > 0;
2984
this.tooltip = this.label;
2985
}
2986
2987
override async run(): Promise<void> {
2988
return this.selectAndInstallExtensions();
2989
}
2990
2991
private async queryExtensionsToInstall(): Promise<IExtension[]> {
2992
const local = await this.extensionsWorkbenchService.queryLocal();
2993
return this.getExtensionsToInstall(local);
2994
}
2995
2996
private async selectAndInstallExtensions(): Promise<void> {
2997
const quickPick = this.quickInputService.createQuickPick<IExtensionPickItem>();
2998
quickPick.busy = true;
2999
const disposable = quickPick.onDidAccept(() => {
3000
disposable.dispose();
3001
quickPick.hide();
3002
quickPick.dispose();
3003
this.onDidAccept(quickPick.selectedItems);
3004
});
3005
quickPick.show();
3006
const localExtensionsToInstall = await this.queryExtensionsToInstall();
3007
quickPick.busy = false;
3008
if (localExtensionsToInstall.length) {
3009
quickPick.title = this.getQuickPickTitle();
3010
quickPick.placeholder = localize('select extensions to install', "Select extensions to install");
3011
quickPick.canSelectMany = true;
3012
localExtensionsToInstall.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName));
3013
quickPick.items = localExtensionsToInstall.map<IExtensionPickItem>(extension => ({ extension, label: extension.displayName, description: extension.version }));
3014
} else {
3015
quickPick.hide();
3016
quickPick.dispose();
3017
this.notificationService.notify({
3018
severity: Severity.Info,
3019
message: localize('no local extensions', "There are no extensions to install.")
3020
});
3021
}
3022
}
3023
3024
private async onDidAccept(selectedItems: ReadonlyArray<IExtensionPickItem>): Promise<void> {
3025
if (selectedItems.length) {
3026
const localExtensionsToInstall = selectedItems.filter(r => !!r.extension).map(r => r.extension);
3027
if (localExtensionsToInstall.length) {
3028
await this.progressService.withProgress(
3029
{
3030
location: ProgressLocation.Notification,
3031
title: localize('installing extensions', "Installing Extensions...")
3032
},
3033
() => this.installExtensions(localExtensionsToInstall));
3034
this.notificationService.info(localize('finished installing', "Successfully installed extensions."));
3035
}
3036
}
3037
}
3038
3039
protected abstract getQuickPickTitle(): string;
3040
protected abstract getExtensionsToInstall(local: IExtension[]): IExtension[];
3041
protected abstract installExtensions(extensions: IExtension[]): Promise<void>;
3042
}
3043
3044
export class InstallLocalExtensionsInRemoteAction extends AbstractInstallExtensionsInServerAction {
3045
3046
constructor(
3047
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
3048
@IQuickInputService quickInputService: IQuickInputService,
3049
@IProgressService progressService: IProgressService,
3050
@INotificationService notificationService: INotificationService,
3051
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
3052
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
3053
@IInstantiationService private readonly instantiationService: IInstantiationService,
3054
@IFileService private readonly fileService: IFileService,
3055
@ILogService private readonly logService: ILogService,
3056
) {
3057
super('workbench.extensions.actions.installLocalExtensionsInRemote', extensionsWorkbenchService, quickInputService, notificationService, progressService);
3058
}
3059
3060
override get label(): string {
3061
if (this.extensionManagementServerService && this.extensionManagementServerService.remoteExtensionManagementServer) {
3062
return localize('select and install local extensions', "Install Local Extensions in '{0}'...", this.extensionManagementServerService.remoteExtensionManagementServer.label);
3063
}
3064
return '';
3065
}
3066
3067
protected getQuickPickTitle(): string {
3068
return localize('install local extensions title', "Install Local Extensions in '{0}'", this.extensionManagementServerService.remoteExtensionManagementServer!.label);
3069
}
3070
3071
protected getExtensionsToInstall(local: IExtension[]): IExtension[] {
3072
return local.filter(extension => {
3073
const action = this.instantiationService.createInstance(RemoteInstallAction, true);
3074
action.extension = extension;
3075
return action.enabled;
3076
});
3077
}
3078
3079
protected async installExtensions(localExtensionsToInstall: IExtension[]): Promise<void> {
3080
const galleryExtensions: IGalleryExtension[] = [];
3081
const vsixs: URI[] = [];
3082
const targetPlatform = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform();
3083
await Promises.settled(localExtensionsToInstall.map(async extension => {
3084
if (this.extensionGalleryService.isEnabled()) {
3085
const gallery = (await this.extensionGalleryService.getExtensions([{ ...extension.identifier, preRelease: !!extension.local?.preRelease }], { targetPlatform, compatible: true }, CancellationToken.None))[0];
3086
if (gallery) {
3087
galleryExtensions.push(gallery);
3088
return;
3089
}
3090
}
3091
const vsix = await this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.zip(extension.local!);
3092
vsixs.push(vsix);
3093
}));
3094
3095
await Promises.settled(galleryExtensions.map(gallery => this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.installFromGallery(gallery)));
3096
try {
3097
await Promises.settled(vsixs.map(vsix => this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.install(vsix)));
3098
} finally {
3099
try {
3100
await Promise.allSettled(vsixs.map(vsix => this.fileService.del(vsix)));
3101
} catch (error) {
3102
this.logService.error(error);
3103
}
3104
}
3105
}
3106
}
3107
3108
export class InstallRemoteExtensionsInLocalAction extends AbstractInstallExtensionsInServerAction {
3109
3110
constructor(
3111
id: string,
3112
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
3113
@IQuickInputService quickInputService: IQuickInputService,
3114
@IProgressService progressService: IProgressService,
3115
@INotificationService notificationService: INotificationService,
3116
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
3117
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
3118
@IFileService private readonly fileService: IFileService,
3119
@ILogService private readonly logService: ILogService,
3120
) {
3121
super(id, extensionsWorkbenchService, quickInputService, notificationService, progressService);
3122
}
3123
3124
override get label(): string {
3125
return localize('select and install remote extensions', "Install Remote Extensions Locally...");
3126
}
3127
3128
protected getQuickPickTitle(): string {
3129
return localize('install remote extensions', "Install Remote Extensions Locally");
3130
}
3131
3132
protected getExtensionsToInstall(local: IExtension[]): IExtension[] {
3133
return local.filter(extension =>
3134
extension.type === ExtensionType.User && extension.server !== this.extensionManagementServerService.localExtensionManagementServer
3135
&& !this.extensionsWorkbenchService.installed.some(e => e.server === this.extensionManagementServerService.localExtensionManagementServer && areSameExtensions(e.identifier, extension.identifier)));
3136
}
3137
3138
protected async installExtensions(extensions: IExtension[]): Promise<void> {
3139
const galleryExtensions: IGalleryExtension[] = [];
3140
const vsixs: URI[] = [];
3141
const targetPlatform = await this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.getTargetPlatform();
3142
await Promises.settled(extensions.map(async extension => {
3143
if (this.extensionGalleryService.isEnabled()) {
3144
const gallery = (await this.extensionGalleryService.getExtensions([{ ...extension.identifier, preRelease: !!extension.local?.preRelease }], { targetPlatform, compatible: true }, CancellationToken.None))[0];
3145
if (gallery) {
3146
galleryExtensions.push(gallery);
3147
return;
3148
}
3149
}
3150
const vsix = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.zip(extension.local!);
3151
vsixs.push(vsix);
3152
}));
3153
3154
await Promises.settled(galleryExtensions.map(gallery => this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.installFromGallery(gallery)));
3155
try {
3156
await Promises.settled(vsixs.map(vsix => this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.install(vsix)));
3157
} finally {
3158
try {
3159
await Promise.allSettled(vsixs.map(vsix => this.fileService.del(vsix)));
3160
} catch (error) {
3161
this.logService.error(error);
3162
}
3163
}
3164
}
3165
}
3166
3167
CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsForLanguage', function (accessor: ServicesAccessor, fileExtension: string) {
3168
const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
3169
return extensionsWorkbenchService.openSearch(`ext:${fileExtension.replace(/^\./, '')}`);
3170
});
3171
3172
export const showExtensionsWithIdsCommandId = 'workbench.extensions.action.showExtensionsWithIds';
3173
CommandsRegistry.registerCommand(showExtensionsWithIdsCommandId, function (accessor: ServicesAccessor, extensionIds: string[]) {
3174
const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
3175
return extensionsWorkbenchService.openSearch(extensionIds.map(id => `@id:${id}`).join(' '));
3176
});
3177
3178
registerColor('extensionButton.background', {
3179
dark: buttonSecondaryBackground,
3180
light: buttonSecondaryBackground,
3181
hcDark: null,
3182
hcLight: null
3183
}, localize('extensionButtonBackground', "Button background color for extension actions."));
3184
3185
registerColor('extensionButton.foreground', {
3186
dark: buttonSecondaryForeground,
3187
light: buttonSecondaryForeground,
3188
hcDark: null,
3189
hcLight: null
3190
}, localize('extensionButtonForeground', "Button foreground color for extension actions."));
3191
3192
registerColor('extensionButton.hoverBackground', {
3193
dark: buttonSecondaryHoverBackground,
3194
light: buttonSecondaryHoverBackground,
3195
hcDark: null,
3196
hcLight: null
3197
}, localize('extensionButtonHoverBackground', "Button background hover color for extension actions."));
3198
3199
registerColor('extensionButton.border', {
3200
dark: buttonBorder,
3201
light: buttonBorder,
3202
hcDark: contrastBorder,
3203
hcLight: contrastBorder
3204
}, localize('extensionButtonBorder', "Button border color for extension actions."));
3205
3206
registerColor('extensionButton.separator', buttonSeparator, localize('extensionButtonSeparator', "Button separator color for extension actions"));
3207
3208
export const extensionButtonProminentBackground = registerColor('extensionButton.prominentBackground', {
3209
dark: buttonBackground,
3210
light: buttonBackground,
3211
hcDark: null,
3212
hcLight: null
3213
}, localize('extensionButtonProminentBackground', "Button background color for extension actions that stand out (e.g. install button)."));
3214
3215
registerColor('extensionButton.prominentForeground', {
3216
dark: buttonForeground,
3217
light: buttonForeground,
3218
hcDark: null,
3219
hcLight: null
3220
}, localize('extensionButtonProminentForeground', "Button foreground color for extension actions that stand out (e.g. install button)."));
3221
3222
registerColor('extensionButton.prominentHoverBackground', {
3223
dark: buttonHoverBackground,
3224
light: buttonHoverBackground,
3225
hcDark: null,
3226
hcLight: null
3227
}, localize('extensionButtonProminentHoverBackground', "Button background hover color for extension actions that stand out (e.g. install button)."));
3228
3229
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
3230
3231
const errorColor = theme.getColor(editorErrorForeground);
3232
if (errorColor) {
3233
collector.addRule(`.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`);
3234
collector.addRule(`.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`);
3235
collector.addRule(`.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`);
3236
}
3237
3238
const warningColor = theme.getColor(editorWarningForeground);
3239
if (warningColor) {
3240
collector.addRule(`.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`);
3241
collector.addRule(`.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`);
3242
collector.addRule(`.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`);
3243
}
3244
3245
const infoColor = theme.getColor(editorInfoForeground);
3246
if (infoColor) {
3247
collector.addRule(`.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`);
3248
collector.addRule(`.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`);
3249
collector.addRule(`.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`);
3250
}
3251
});
3252
3253