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