@theia/vsx-registry 1.53.0-next.4 → 1.53.0-next.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -45
- package/lib/common/vsx-environment.d.ts +1 -0
- package/lib/common/vsx-environment.d.ts.map +1 -1
- package/lib/common/vsx-registry-common-module.d.ts.map +1 -1
- package/lib/common/vsx-registry-common-module.js +9 -3
- package/lib/common/vsx-registry-common-module.js.map +1 -1
- package/lib/node/vsx-cli.d.ts +1 -0
- package/lib/node/vsx-cli.d.ts.map +1 -1
- package/lib/node/vsx-cli.js +4 -0
- package/lib/node/vsx-cli.js.map +1 -1
- package/lib/node/vsx-environment-impl.d.ts +1 -0
- package/lib/node/vsx-environment-impl.d.ts.map +1 -1
- package/lib/node/vsx-environment-impl.js +3 -0
- package/lib/node/vsx-environment-impl.js.map +1 -1
- package/lib/node/vsx-extension-resolver.js +3 -3
- package/lib/node/vsx-extension-resolver.js.map +1 -1
- package/package.json +12 -11
- package/src/browser/recommended-extensions/preference-provider-overrides.ts +99 -99
- package/src/browser/recommended-extensions/recommended-extensions-json-schema.ts +74 -74
- package/src/browser/recommended-extensions/recommended-extensions-preference-contribution.ts +68 -68
- package/src/browser/style/extensions.svg +4 -4
- package/src/browser/style/index.css +436 -436
- package/src/browser/vsx-extension-argument-processor.ts +32 -32
- package/src/browser/vsx-extension-commands.ts +68 -68
- package/src/browser/vsx-extension-editor-manager.ts +42 -42
- package/src/browser/vsx-extension-editor.tsx +96 -96
- package/src/browser/vsx-extension.tsx +710 -710
- package/src/browser/vsx-extensions-contribution.ts +373 -373
- package/src/browser/vsx-extensions-model.ts +456 -456
- package/src/browser/vsx-extensions-preferences.ts +58 -58
- package/src/browser/vsx-extensions-search-bar.tsx +107 -107
- package/src/browser/vsx-extensions-search-model.ts +61 -61
- package/src/browser/vsx-extensions-source.ts +83 -83
- package/src/browser/vsx-extensions-view-container.ts +179 -179
- package/src/browser/vsx-extensions-widget.tsx +165 -165
- package/src/browser/vsx-language-quick-pick-service.ts +112 -112
- package/src/browser/vsx-registry-frontend-module.ts +113 -113
- package/src/common/index.ts +19 -19
- package/src/common/ovsx-client-provider.ts +35 -35
- package/src/common/vsx-environment.ts +28 -27
- package/src/common/vsx-extension-uri.ts +20 -20
- package/src/common/vsx-registry-common-module.ts +85 -78
- package/src/node/vsx-cli-deployer-participant.ts +46 -46
- package/src/node/vsx-cli.ts +55 -51
- package/src/node/vsx-environment-impl.ts +54 -50
- package/src/node/vsx-extension-resolver.ts +134 -134
- package/src/node/vsx-registry-backend-module.ts +38 -38
- package/src/node/vsx-remote-cli.ts +39 -39
- package/src/package.spec.ts +29 -29
|
@@ -1,373 +1,373 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2020 TypeFox and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import { CommonMenus, LabelProvider, PreferenceService, QuickInputService, QuickPickItem } from '@theia/core/lib/browser';
|
|
18
|
-
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
|
19
|
-
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
|
20
|
-
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
|
21
|
-
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
|
22
|
-
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
|
23
|
-
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
|
24
|
-
import { CompoundMenuNodeRole, MenuModelRegistry, MessageService, SelectionService, nls } from '@theia/core/lib/common';
|
|
25
|
-
import { Color } from '@theia/core/lib/common/color';
|
|
26
|
-
import { Command, CommandRegistry } from '@theia/core/lib/common/command';
|
|
27
|
-
import URI from '@theia/core/lib/common/uri';
|
|
28
|
-
import { UriAwareCommandHandler } from '@theia/core/lib/common/uri-command-handler';
|
|
29
|
-
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
30
|
-
import { FileDialogService, OpenFileDialogProps } from '@theia/filesystem/lib/browser';
|
|
31
|
-
import { NAVIGATOR_CONTEXT_MENU } from '@theia/navigator/lib/browser/navigator-contribution';
|
|
32
|
-
import { OVSXApiFilterProvider, VSXExtensionRaw } from '@theia/ovsx-client';
|
|
33
|
-
import { VscodeCommands } from '@theia/plugin-ext-vscode/lib/browser/plugin-vscode-commands-contribution';
|
|
34
|
-
import { DateTime } from 'luxon';
|
|
35
|
-
import { OVSXClientProvider } from '../common/ovsx-client-provider';
|
|
36
|
-
import { IGNORE_RECOMMENDATIONS_ID } from './recommended-extensions/recommended-extensions-preference-contribution';
|
|
37
|
-
import { VSXExtension, VSXExtensionsContextMenu } from './vsx-extension';
|
|
38
|
-
import { VSXExtensionsCommands } from './vsx-extension-commands';
|
|
39
|
-
import { VSXExtensionsModel } from './vsx-extensions-model';
|
|
40
|
-
import { BUILTIN_QUERY, INSTALLED_QUERY, RECOMMENDED_QUERY } from './vsx-extensions-search-model';
|
|
41
|
-
import { VSXExtensionsViewContainer } from './vsx-extensions-view-container';
|
|
42
|
-
import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
|
|
43
|
-
import debounce = require('@theia/core/shared/lodash.debounce');
|
|
44
|
-
|
|
45
|
-
export namespace VSXCommands {
|
|
46
|
-
export const TOGGLE_EXTENSIONS: Command = {
|
|
47
|
-
id: 'vsxExtensions.toggle',
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@injectable()
|
|
52
|
-
export class VSXExtensionsContribution extends AbstractViewContribution<VSXExtensionsViewContainer> implements ColorContribution, FrontendApplicationContribution {
|
|
53
|
-
|
|
54
|
-
@inject(VSXExtensionsModel) protected model: VSXExtensionsModel;
|
|
55
|
-
@inject(CommandRegistry) protected commandRegistry: CommandRegistry;
|
|
56
|
-
@inject(FileDialogService) protected fileDialogService: FileDialogService;
|
|
57
|
-
@inject(MessageService) protected messageService: MessageService;
|
|
58
|
-
@inject(LabelProvider) protected labelProvider: LabelProvider;
|
|
59
|
-
@inject(ClipboardService) protected clipboardService: ClipboardService;
|
|
60
|
-
@inject(PreferenceService) protected preferenceService: PreferenceService;
|
|
61
|
-
@inject(OVSXClientProvider) protected clientProvider: OVSXClientProvider;
|
|
62
|
-
@inject(OVSXApiFilterProvider) protected vsxApiFilter: OVSXApiFilterProvider;
|
|
63
|
-
@inject(ApplicationServer) protected applicationServer: ApplicationServer;
|
|
64
|
-
@inject(QuickInputService) protected quickInput: QuickInputService;
|
|
65
|
-
@inject(SelectionService) protected readonly selectionService: SelectionService;
|
|
66
|
-
|
|
67
|
-
constructor() {
|
|
68
|
-
super({
|
|
69
|
-
widgetId: VSXExtensionsViewContainer.ID,
|
|
70
|
-
widgetName: VSXExtensionsViewContainer.LABEL,
|
|
71
|
-
defaultWidgetOptions: {
|
|
72
|
-
area: 'left',
|
|
73
|
-
rank: 500
|
|
74
|
-
},
|
|
75
|
-
toggleCommandId: VSXCommands.TOGGLE_EXTENSIONS.id,
|
|
76
|
-
toggleKeybinding: 'ctrlcmd+shift+x'
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
@postConstruct()
|
|
81
|
-
protected init(): void {
|
|
82
|
-
const oneShotDisposable = this.model.onDidChange(debounce(() => {
|
|
83
|
-
this.showRecommendedToast();
|
|
84
|
-
oneShotDisposable.dispose();
|
|
85
|
-
}, 5000, { trailing: true }));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async initializeLayout(app: FrontendApplication): Promise<void> {
|
|
89
|
-
await this.openView({ activate: false });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
override registerCommands(commands: CommandRegistry): void {
|
|
93
|
-
super.registerCommands(commands);
|
|
94
|
-
commands.registerCommand(VSXExtensionsCommands.CLEAR_ALL, {
|
|
95
|
-
execute: () => this.model.search.query = '',
|
|
96
|
-
isEnabled: () => !!this.model.search.query,
|
|
97
|
-
isVisible: () => true,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
commands.registerCommand(VSXExtensionsCommands.INSTALL_FROM_VSIX, {
|
|
101
|
-
execute: () => this.installFromVSIX()
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
commands.registerCommand(VSXExtensionsCommands.INSTALL_VSIX_FILE,
|
|
105
|
-
UriAwareCommandHandler.MonoSelect(this.selectionService, {
|
|
106
|
-
execute: fileURI => this.installVsixFile(fileURI),
|
|
107
|
-
isEnabled: fileURI => fileURI.scheme === 'file' && fileURI.path.ext === '.vsix'
|
|
108
|
-
})
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
commands.registerCommand(VSXExtensionsCommands.INSTALL_ANOTHER_VERSION, {
|
|
112
|
-
// Check downloadUrl to ensure we have an idea of where to look for other versions.
|
|
113
|
-
isEnabled: (extension: VSXExtension) => !extension.builtin && !!extension.downloadUrl,
|
|
114
|
-
execute: async (extension: VSXExtension) => this.installAnotherVersion(extension),
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
commands.registerCommand(VSXExtensionsCommands.COPY, {
|
|
118
|
-
execute: (extension: VSXExtension) => this.copy(extension)
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
commands.registerCommand(VSXExtensionsCommands.COPY_EXTENSION_ID, {
|
|
122
|
-
execute: (extension: VSXExtension) => this.copyExtensionId(extension)
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
commands.registerCommand(VSXExtensionsCommands.SHOW_BUILTINS, {
|
|
126
|
-
execute: () => this.showBuiltinExtensions()
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
commands.registerCommand(VSXExtensionsCommands.SHOW_INSTALLED, {
|
|
130
|
-
execute: () => this.showInstalledExtensions()
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
commands.registerCommand(VSXExtensionsCommands.SHOW_RECOMMENDATIONS, {
|
|
134
|
-
execute: () => this.showRecommendedExtensions()
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
override registerMenus(menus: MenuModelRegistry): void {
|
|
139
|
-
super.registerMenus(menus);
|
|
140
|
-
menus.registerMenuAction(CommonMenus.MANAGE_SETTINGS, {
|
|
141
|
-
commandId: VSXCommands.TOGGLE_EXTENSIONS.id,
|
|
142
|
-
label: nls.localizeByDefault('Extensions'),
|
|
143
|
-
order: 'a20'
|
|
144
|
-
});
|
|
145
|
-
menus.registerMenuAction(VSXExtensionsContextMenu.COPY, {
|
|
146
|
-
commandId: VSXExtensionsCommands.COPY.id,
|
|
147
|
-
label: nls.localizeByDefault('Copy'),
|
|
148
|
-
order: '0'
|
|
149
|
-
});
|
|
150
|
-
menus.registerMenuAction(VSXExtensionsContextMenu.COPY, {
|
|
151
|
-
commandId: VSXExtensionsCommands.COPY_EXTENSION_ID.id,
|
|
152
|
-
label: nls.localizeByDefault('Copy Extension ID'),
|
|
153
|
-
order: '1'
|
|
154
|
-
});
|
|
155
|
-
menus.registerMenuAction(VSXExtensionsContextMenu.INSTALL, {
|
|
156
|
-
commandId: VSXExtensionsCommands.INSTALL_ANOTHER_VERSION.id,
|
|
157
|
-
label: nls.localizeByDefault('Install Another Version...'),
|
|
158
|
-
});
|
|
159
|
-
menus.registerMenuAction(NAVIGATOR_CONTEXT_MENU, {
|
|
160
|
-
commandId: VSXExtensionsCommands.INSTALL_VSIX_FILE.id,
|
|
161
|
-
label: VSXExtensionsCommands.INSTALL_VSIX_FILE.label,
|
|
162
|
-
when: 'resourceScheme == file && resourceExtname == .vsix'
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
menus.registerSubmenu(VSXExtensionsContextMenu.CONTRIBUTION, '', {
|
|
166
|
-
role: CompoundMenuNodeRole.Group,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
registerColors(colors: ColorRegistry): void {
|
|
171
|
-
// VS Code colors should be aligned with https://code.visualstudio.com/api/references/theme-color#extensions
|
|
172
|
-
colors.register(
|
|
173
|
-
{
|
|
174
|
-
id: 'extensionButton.prominentBackground', defaults: {
|
|
175
|
-
dark: '#327e36',
|
|
176
|
-
light: '#327e36'
|
|
177
|
-
}, description: 'Button background color for actions extension that stand out (e.g. install button).'
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
id: 'extensionButton.prominentForeground', defaults: {
|
|
181
|
-
dark: Color.white,
|
|
182
|
-
light: Color.white
|
|
183
|
-
}, description: 'Button foreground color for actions extension that stand out (e.g. install button).'
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
id: 'extensionButton.prominentHoverBackground', defaults: {
|
|
187
|
-
dark: '#28632b',
|
|
188
|
-
light: '#28632b'
|
|
189
|
-
}, description: 'Button background hover color for actions extension that stand out (e.g. install button).'
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
id: 'extensionEditor.tableHeadBorder', defaults: {
|
|
193
|
-
dark: Color.transparent('#ffffff', 0.7),
|
|
194
|
-
light: Color.transparent('#000000', 0.7),
|
|
195
|
-
hcDark: Color.white,
|
|
196
|
-
hcLight: Color.black
|
|
197
|
-
}, description: 'Border color for the table head row of the extension editor view'
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
id: 'extensionEditor.tableCellBorder', defaults: {
|
|
201
|
-
dark: Color.transparent('#ffffff', 0.2),
|
|
202
|
-
light: Color.transparent('#000000', 0.2),
|
|
203
|
-
hcDark: Color.white,
|
|
204
|
-
hcLight: Color.black
|
|
205
|
-
}, description: 'Border color for a table row of the extension editor view'
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
id: 'extensionIcon.verifiedForeground', defaults: {
|
|
209
|
-
dark: '#40a6ff',
|
|
210
|
-
light: '#40a6ff'
|
|
211
|
-
}, description: 'The icon color for extension verified publisher.'
|
|
212
|
-
},
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Installs a local .vsix file after prompting the `Open File` dialog. Resolves to the URI of the file.
|
|
218
|
-
*/
|
|
219
|
-
protected async installFromVSIX(): Promise<void> {
|
|
220
|
-
const props: OpenFileDialogProps = {
|
|
221
|
-
title: VSXExtensionsCommands.INSTALL_FROM_VSIX.dialogLabel,
|
|
222
|
-
openLabel: nls.localizeByDefault('Install from VSIX'),
|
|
223
|
-
filters: { 'VSIX Extensions (*.vsix)': ['vsix'] },
|
|
224
|
-
canSelectMany: false,
|
|
225
|
-
canSelectFiles: true
|
|
226
|
-
};
|
|
227
|
-
const extensionUri = await this.fileDialogService.showOpenDialog(props);
|
|
228
|
-
if (extensionUri) {
|
|
229
|
-
if (extensionUri.path.ext === '.vsix') {
|
|
230
|
-
await this.installVsixFile(extensionUri);
|
|
231
|
-
} else {
|
|
232
|
-
this.messageService.error(nls.localize('theia/vsx-registry/invalidVSIX', 'The selected file is not a valid "*.vsix" plugin.'));
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Installs a local vs-code extension file.
|
|
239
|
-
* The implementation doesn't check if the file is a valid VSIX file, or the URI has a *.vsix extension.
|
|
240
|
-
* The caller should ensure the file is a valid VSIX file.
|
|
241
|
-
*
|
|
242
|
-
* @param fileURI the URI of the file to install.
|
|
243
|
-
*/
|
|
244
|
-
protected async installVsixFile(fileURI: URI): Promise<void> {
|
|
245
|
-
const extensionName = this.labelProvider.getName(fileURI);
|
|
246
|
-
try {
|
|
247
|
-
await this.commandRegistry.executeCommand(VscodeCommands.INSTALL_FROM_VSIX.id, fileURI);
|
|
248
|
-
this.messageService.info(nls.localizeByDefault('Completed installing {0} extension from VSIX.', extensionName));
|
|
249
|
-
} catch (e) {
|
|
250
|
-
this.messageService.error(nls.localize('theia/vsx-registry/failedInstallingVSIX', 'Failed to install {0} from VSIX.', extensionName));
|
|
251
|
-
console.warn(e);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Given an extension, displays a quick pick of other compatible versions and installs the selected version.
|
|
257
|
-
*
|
|
258
|
-
* @param extension a VSX extension.
|
|
259
|
-
*/
|
|
260
|
-
protected async installAnotherVersion(extension: VSXExtension): Promise<void> {
|
|
261
|
-
const extensionId = extension.id;
|
|
262
|
-
const currentVersion = extension.version;
|
|
263
|
-
const client = await this.clientProvider();
|
|
264
|
-
const filter = await this.vsxApiFilter();
|
|
265
|
-
const targetPlatform = await this.applicationServer.getApplicationPlatform();
|
|
266
|
-
const { extensions } = await client.query({ extensionId, includeAllVersions: true });
|
|
267
|
-
const latestCompatible = await filter.findLatestCompatibleExtension({
|
|
268
|
-
extensionId,
|
|
269
|
-
includeAllVersions: true,
|
|
270
|
-
targetPlatform
|
|
271
|
-
});
|
|
272
|
-
let compatibleExtensions: VSXExtensionRaw[] = [];
|
|
273
|
-
let activeItem = undefined;
|
|
274
|
-
if (latestCompatible) {
|
|
275
|
-
compatibleExtensions = extensions.slice(extensions.findIndex(ext => ext.version === latestCompatible.version));
|
|
276
|
-
}
|
|
277
|
-
const items: QuickPickItem[] = compatibleExtensions.map(ext => {
|
|
278
|
-
const item = {
|
|
279
|
-
label: ext.version,
|
|
280
|
-
description: DateTime.fromISO(ext.timestamp).toRelative({ locale: nls.locale }) ?? ''
|
|
281
|
-
};
|
|
282
|
-
if (currentVersion === ext.version) {
|
|
283
|
-
item.description += ` (${nls.localizeByDefault('Current')})`;
|
|
284
|
-
activeItem = item;
|
|
285
|
-
}
|
|
286
|
-
return item;
|
|
287
|
-
});
|
|
288
|
-
const selectedItem = await this.quickInput.showQuickPick(items, {
|
|
289
|
-
placeholder: nls.localizeByDefault('Select Version to Install'),
|
|
290
|
-
runIfSingle: false,
|
|
291
|
-
activeItem
|
|
292
|
-
});
|
|
293
|
-
if (selectedItem) {
|
|
294
|
-
const selectedExtension = this.model.getExtension(extensionId);
|
|
295
|
-
if (selectedExtension) {
|
|
296
|
-
await this.updateVersion(selectedExtension, selectedItem.label);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
protected async copy(extension: VSXExtension): Promise<void> {
|
|
302
|
-
this.clipboardService.writeText(await extension.serialize());
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
protected copyExtensionId(extension: VSXExtension): void {
|
|
306
|
-
this.clipboardService.writeText(extension.id);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Updates an extension to a specific version.
|
|
311
|
-
*
|
|
312
|
-
* @param extension the extension to update.
|
|
313
|
-
* @param updateToVersion the version to update to.
|
|
314
|
-
* @param revertToVersion the version to revert to (in case of failure).
|
|
315
|
-
*/
|
|
316
|
-
protected async updateVersion(extension: VSXExtension, updateToVersion: string): Promise<void> {
|
|
317
|
-
try {
|
|
318
|
-
await extension.install({ version: updateToVersion, ignoreOtherVersions: true });
|
|
319
|
-
} catch {
|
|
320
|
-
this.messageService.warn(nls.localize('theia/vsx-registry/vsx-extensions-contribution/update-version-version-error', 'Failed to install version {0} of {1}.',
|
|
321
|
-
updateToVersion, extension.displayName));
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
try {
|
|
325
|
-
if (extension.version !== updateToVersion) {
|
|
326
|
-
await extension.uninstall();
|
|
327
|
-
}
|
|
328
|
-
} catch {
|
|
329
|
-
this.messageService.warn(nls.localize('theia/vsx-registry/vsx-extensions-contribution/update-version-uninstall-error', 'Error while removing the extension: {0}.',
|
|
330
|
-
extension.displayName));
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
protected async showRecommendedToast(): Promise<void> {
|
|
335
|
-
if (!this.preferenceService.get(IGNORE_RECOMMENDATIONS_ID, false)) {
|
|
336
|
-
const recommended = new Set([...this.model.recommended]);
|
|
337
|
-
for (const installed of this.model.installed) {
|
|
338
|
-
recommended.delete(installed);
|
|
339
|
-
}
|
|
340
|
-
if (recommended.size) {
|
|
341
|
-
const install = nls.localizeByDefault('Install');
|
|
342
|
-
const showRecommendations = nls.localizeByDefault('Show Recommendations');
|
|
343
|
-
const userResponse = await this.messageService.info(
|
|
344
|
-
nls.localize('theia/vsx-registry/recommendedExtensions', 'Do you want to install the recommended extensions for this repository?'),
|
|
345
|
-
install,
|
|
346
|
-
showRecommendations
|
|
347
|
-
);
|
|
348
|
-
if (userResponse === install) {
|
|
349
|
-
for (const recommendation of recommended) {
|
|
350
|
-
this.model.getExtension(recommendation)?.install();
|
|
351
|
-
}
|
|
352
|
-
} else if (userResponse === showRecommendations) {
|
|
353
|
-
await this.showRecommendedExtensions();
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
protected async showBuiltinExtensions(): Promise<void> {
|
|
360
|
-
await this.openView({ activate: true });
|
|
361
|
-
this.model.search.query = BUILTIN_QUERY;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
protected async showInstalledExtensions(): Promise<void> {
|
|
365
|
-
await this.openView({ activate: true });
|
|
366
|
-
this.model.search.query = INSTALLED_QUERY;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
protected async showRecommendedExtensions(): Promise<void> {
|
|
370
|
-
await this.openView({ activate: true });
|
|
371
|
-
this.model.search.query = RECOMMENDED_QUERY;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2020 TypeFox and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { CommonMenus, LabelProvider, PreferenceService, QuickInputService, QuickPickItem } from '@theia/core/lib/browser';
|
|
18
|
+
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
|
19
|
+
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
|
20
|
+
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
|
21
|
+
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
|
22
|
+
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
|
23
|
+
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
|
24
|
+
import { CompoundMenuNodeRole, MenuModelRegistry, MessageService, SelectionService, nls } from '@theia/core/lib/common';
|
|
25
|
+
import { Color } from '@theia/core/lib/common/color';
|
|
26
|
+
import { Command, CommandRegistry } from '@theia/core/lib/common/command';
|
|
27
|
+
import URI from '@theia/core/lib/common/uri';
|
|
28
|
+
import { UriAwareCommandHandler } from '@theia/core/lib/common/uri-command-handler';
|
|
29
|
+
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
30
|
+
import { FileDialogService, OpenFileDialogProps } from '@theia/filesystem/lib/browser';
|
|
31
|
+
import { NAVIGATOR_CONTEXT_MENU } from '@theia/navigator/lib/browser/navigator-contribution';
|
|
32
|
+
import { OVSXApiFilterProvider, VSXExtensionRaw } from '@theia/ovsx-client';
|
|
33
|
+
import { VscodeCommands } from '@theia/plugin-ext-vscode/lib/browser/plugin-vscode-commands-contribution';
|
|
34
|
+
import { DateTime } from 'luxon';
|
|
35
|
+
import { OVSXClientProvider } from '../common/ovsx-client-provider';
|
|
36
|
+
import { IGNORE_RECOMMENDATIONS_ID } from './recommended-extensions/recommended-extensions-preference-contribution';
|
|
37
|
+
import { VSXExtension, VSXExtensionsContextMenu } from './vsx-extension';
|
|
38
|
+
import { VSXExtensionsCommands } from './vsx-extension-commands';
|
|
39
|
+
import { VSXExtensionsModel } from './vsx-extensions-model';
|
|
40
|
+
import { BUILTIN_QUERY, INSTALLED_QUERY, RECOMMENDED_QUERY } from './vsx-extensions-search-model';
|
|
41
|
+
import { VSXExtensionsViewContainer } from './vsx-extensions-view-container';
|
|
42
|
+
import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
|
|
43
|
+
import debounce = require('@theia/core/shared/lodash.debounce');
|
|
44
|
+
|
|
45
|
+
export namespace VSXCommands {
|
|
46
|
+
export const TOGGLE_EXTENSIONS: Command = {
|
|
47
|
+
id: 'vsxExtensions.toggle',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@injectable()
|
|
52
|
+
export class VSXExtensionsContribution extends AbstractViewContribution<VSXExtensionsViewContainer> implements ColorContribution, FrontendApplicationContribution {
|
|
53
|
+
|
|
54
|
+
@inject(VSXExtensionsModel) protected model: VSXExtensionsModel;
|
|
55
|
+
@inject(CommandRegistry) protected commandRegistry: CommandRegistry;
|
|
56
|
+
@inject(FileDialogService) protected fileDialogService: FileDialogService;
|
|
57
|
+
@inject(MessageService) protected messageService: MessageService;
|
|
58
|
+
@inject(LabelProvider) protected labelProvider: LabelProvider;
|
|
59
|
+
@inject(ClipboardService) protected clipboardService: ClipboardService;
|
|
60
|
+
@inject(PreferenceService) protected preferenceService: PreferenceService;
|
|
61
|
+
@inject(OVSXClientProvider) protected clientProvider: OVSXClientProvider;
|
|
62
|
+
@inject(OVSXApiFilterProvider) protected vsxApiFilter: OVSXApiFilterProvider;
|
|
63
|
+
@inject(ApplicationServer) protected applicationServer: ApplicationServer;
|
|
64
|
+
@inject(QuickInputService) protected quickInput: QuickInputService;
|
|
65
|
+
@inject(SelectionService) protected readonly selectionService: SelectionService;
|
|
66
|
+
|
|
67
|
+
constructor() {
|
|
68
|
+
super({
|
|
69
|
+
widgetId: VSXExtensionsViewContainer.ID,
|
|
70
|
+
widgetName: VSXExtensionsViewContainer.LABEL,
|
|
71
|
+
defaultWidgetOptions: {
|
|
72
|
+
area: 'left',
|
|
73
|
+
rank: 500
|
|
74
|
+
},
|
|
75
|
+
toggleCommandId: VSXCommands.TOGGLE_EXTENSIONS.id,
|
|
76
|
+
toggleKeybinding: 'ctrlcmd+shift+x'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@postConstruct()
|
|
81
|
+
protected init(): void {
|
|
82
|
+
const oneShotDisposable = this.model.onDidChange(debounce(() => {
|
|
83
|
+
this.showRecommendedToast();
|
|
84
|
+
oneShotDisposable.dispose();
|
|
85
|
+
}, 5000, { trailing: true }));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async initializeLayout(app: FrontendApplication): Promise<void> {
|
|
89
|
+
await this.openView({ activate: false });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override registerCommands(commands: CommandRegistry): void {
|
|
93
|
+
super.registerCommands(commands);
|
|
94
|
+
commands.registerCommand(VSXExtensionsCommands.CLEAR_ALL, {
|
|
95
|
+
execute: () => this.model.search.query = '',
|
|
96
|
+
isEnabled: () => !!this.model.search.query,
|
|
97
|
+
isVisible: () => true,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
commands.registerCommand(VSXExtensionsCommands.INSTALL_FROM_VSIX, {
|
|
101
|
+
execute: () => this.installFromVSIX()
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
commands.registerCommand(VSXExtensionsCommands.INSTALL_VSIX_FILE,
|
|
105
|
+
UriAwareCommandHandler.MonoSelect(this.selectionService, {
|
|
106
|
+
execute: fileURI => this.installVsixFile(fileURI),
|
|
107
|
+
isEnabled: fileURI => fileURI.scheme === 'file' && fileURI.path.ext === '.vsix'
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
commands.registerCommand(VSXExtensionsCommands.INSTALL_ANOTHER_VERSION, {
|
|
112
|
+
// Check downloadUrl to ensure we have an idea of where to look for other versions.
|
|
113
|
+
isEnabled: (extension: VSXExtension) => !extension.builtin && !!extension.downloadUrl,
|
|
114
|
+
execute: async (extension: VSXExtension) => this.installAnotherVersion(extension),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
commands.registerCommand(VSXExtensionsCommands.COPY, {
|
|
118
|
+
execute: (extension: VSXExtension) => this.copy(extension)
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
commands.registerCommand(VSXExtensionsCommands.COPY_EXTENSION_ID, {
|
|
122
|
+
execute: (extension: VSXExtension) => this.copyExtensionId(extension)
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
commands.registerCommand(VSXExtensionsCommands.SHOW_BUILTINS, {
|
|
126
|
+
execute: () => this.showBuiltinExtensions()
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
commands.registerCommand(VSXExtensionsCommands.SHOW_INSTALLED, {
|
|
130
|
+
execute: () => this.showInstalledExtensions()
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
commands.registerCommand(VSXExtensionsCommands.SHOW_RECOMMENDATIONS, {
|
|
134
|
+
execute: () => this.showRecommendedExtensions()
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
override registerMenus(menus: MenuModelRegistry): void {
|
|
139
|
+
super.registerMenus(menus);
|
|
140
|
+
menus.registerMenuAction(CommonMenus.MANAGE_SETTINGS, {
|
|
141
|
+
commandId: VSXCommands.TOGGLE_EXTENSIONS.id,
|
|
142
|
+
label: nls.localizeByDefault('Extensions'),
|
|
143
|
+
order: 'a20'
|
|
144
|
+
});
|
|
145
|
+
menus.registerMenuAction(VSXExtensionsContextMenu.COPY, {
|
|
146
|
+
commandId: VSXExtensionsCommands.COPY.id,
|
|
147
|
+
label: nls.localizeByDefault('Copy'),
|
|
148
|
+
order: '0'
|
|
149
|
+
});
|
|
150
|
+
menus.registerMenuAction(VSXExtensionsContextMenu.COPY, {
|
|
151
|
+
commandId: VSXExtensionsCommands.COPY_EXTENSION_ID.id,
|
|
152
|
+
label: nls.localizeByDefault('Copy Extension ID'),
|
|
153
|
+
order: '1'
|
|
154
|
+
});
|
|
155
|
+
menus.registerMenuAction(VSXExtensionsContextMenu.INSTALL, {
|
|
156
|
+
commandId: VSXExtensionsCommands.INSTALL_ANOTHER_VERSION.id,
|
|
157
|
+
label: nls.localizeByDefault('Install Another Version...'),
|
|
158
|
+
});
|
|
159
|
+
menus.registerMenuAction(NAVIGATOR_CONTEXT_MENU, {
|
|
160
|
+
commandId: VSXExtensionsCommands.INSTALL_VSIX_FILE.id,
|
|
161
|
+
label: VSXExtensionsCommands.INSTALL_VSIX_FILE.label,
|
|
162
|
+
when: 'resourceScheme == file && resourceExtname == .vsix'
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
menus.registerSubmenu(VSXExtensionsContextMenu.CONTRIBUTION, '', {
|
|
166
|
+
role: CompoundMenuNodeRole.Group,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
registerColors(colors: ColorRegistry): void {
|
|
171
|
+
// VS Code colors should be aligned with https://code.visualstudio.com/api/references/theme-color#extensions
|
|
172
|
+
colors.register(
|
|
173
|
+
{
|
|
174
|
+
id: 'extensionButton.prominentBackground', defaults: {
|
|
175
|
+
dark: '#327e36',
|
|
176
|
+
light: '#327e36'
|
|
177
|
+
}, description: 'Button background color for actions extension that stand out (e.g. install button).'
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'extensionButton.prominentForeground', defaults: {
|
|
181
|
+
dark: Color.white,
|
|
182
|
+
light: Color.white
|
|
183
|
+
}, description: 'Button foreground color for actions extension that stand out (e.g. install button).'
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'extensionButton.prominentHoverBackground', defaults: {
|
|
187
|
+
dark: '#28632b',
|
|
188
|
+
light: '#28632b'
|
|
189
|
+
}, description: 'Button background hover color for actions extension that stand out (e.g. install button).'
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: 'extensionEditor.tableHeadBorder', defaults: {
|
|
193
|
+
dark: Color.transparent('#ffffff', 0.7),
|
|
194
|
+
light: Color.transparent('#000000', 0.7),
|
|
195
|
+
hcDark: Color.white,
|
|
196
|
+
hcLight: Color.black
|
|
197
|
+
}, description: 'Border color for the table head row of the extension editor view'
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'extensionEditor.tableCellBorder', defaults: {
|
|
201
|
+
dark: Color.transparent('#ffffff', 0.2),
|
|
202
|
+
light: Color.transparent('#000000', 0.2),
|
|
203
|
+
hcDark: Color.white,
|
|
204
|
+
hcLight: Color.black
|
|
205
|
+
}, description: 'Border color for a table row of the extension editor view'
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: 'extensionIcon.verifiedForeground', defaults: {
|
|
209
|
+
dark: '#40a6ff',
|
|
210
|
+
light: '#40a6ff'
|
|
211
|
+
}, description: 'The icon color for extension verified publisher.'
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Installs a local .vsix file after prompting the `Open File` dialog. Resolves to the URI of the file.
|
|
218
|
+
*/
|
|
219
|
+
protected async installFromVSIX(): Promise<void> {
|
|
220
|
+
const props: OpenFileDialogProps = {
|
|
221
|
+
title: VSXExtensionsCommands.INSTALL_FROM_VSIX.dialogLabel,
|
|
222
|
+
openLabel: nls.localizeByDefault('Install from VSIX'),
|
|
223
|
+
filters: { 'VSIX Extensions (*.vsix)': ['vsix'] },
|
|
224
|
+
canSelectMany: false,
|
|
225
|
+
canSelectFiles: true
|
|
226
|
+
};
|
|
227
|
+
const extensionUri = await this.fileDialogService.showOpenDialog(props);
|
|
228
|
+
if (extensionUri) {
|
|
229
|
+
if (extensionUri.path.ext === '.vsix') {
|
|
230
|
+
await this.installVsixFile(extensionUri);
|
|
231
|
+
} else {
|
|
232
|
+
this.messageService.error(nls.localize('theia/vsx-registry/invalidVSIX', 'The selected file is not a valid "*.vsix" plugin.'));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Installs a local vs-code extension file.
|
|
239
|
+
* The implementation doesn't check if the file is a valid VSIX file, or the URI has a *.vsix extension.
|
|
240
|
+
* The caller should ensure the file is a valid VSIX file.
|
|
241
|
+
*
|
|
242
|
+
* @param fileURI the URI of the file to install.
|
|
243
|
+
*/
|
|
244
|
+
protected async installVsixFile(fileURI: URI): Promise<void> {
|
|
245
|
+
const extensionName = this.labelProvider.getName(fileURI);
|
|
246
|
+
try {
|
|
247
|
+
await this.commandRegistry.executeCommand(VscodeCommands.INSTALL_FROM_VSIX.id, fileURI);
|
|
248
|
+
this.messageService.info(nls.localizeByDefault('Completed installing {0} extension from VSIX.', extensionName));
|
|
249
|
+
} catch (e) {
|
|
250
|
+
this.messageService.error(nls.localize('theia/vsx-registry/failedInstallingVSIX', 'Failed to install {0} from VSIX.', extensionName));
|
|
251
|
+
console.warn(e);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Given an extension, displays a quick pick of other compatible versions and installs the selected version.
|
|
257
|
+
*
|
|
258
|
+
* @param extension a VSX extension.
|
|
259
|
+
*/
|
|
260
|
+
protected async installAnotherVersion(extension: VSXExtension): Promise<void> {
|
|
261
|
+
const extensionId = extension.id;
|
|
262
|
+
const currentVersion = extension.version;
|
|
263
|
+
const client = await this.clientProvider();
|
|
264
|
+
const filter = await this.vsxApiFilter();
|
|
265
|
+
const targetPlatform = await this.applicationServer.getApplicationPlatform();
|
|
266
|
+
const { extensions } = await client.query({ extensionId, includeAllVersions: true });
|
|
267
|
+
const latestCompatible = await filter.findLatestCompatibleExtension({
|
|
268
|
+
extensionId,
|
|
269
|
+
includeAllVersions: true,
|
|
270
|
+
targetPlatform
|
|
271
|
+
});
|
|
272
|
+
let compatibleExtensions: VSXExtensionRaw[] = [];
|
|
273
|
+
let activeItem = undefined;
|
|
274
|
+
if (latestCompatible) {
|
|
275
|
+
compatibleExtensions = extensions.slice(extensions.findIndex(ext => ext.version === latestCompatible.version));
|
|
276
|
+
}
|
|
277
|
+
const items: QuickPickItem[] = compatibleExtensions.map(ext => {
|
|
278
|
+
const item = {
|
|
279
|
+
label: ext.version,
|
|
280
|
+
description: DateTime.fromISO(ext.timestamp).toRelative({ locale: nls.locale }) ?? ''
|
|
281
|
+
};
|
|
282
|
+
if (currentVersion === ext.version) {
|
|
283
|
+
item.description += ` (${nls.localizeByDefault('Current')})`;
|
|
284
|
+
activeItem = item;
|
|
285
|
+
}
|
|
286
|
+
return item;
|
|
287
|
+
});
|
|
288
|
+
const selectedItem = await this.quickInput.showQuickPick(items, {
|
|
289
|
+
placeholder: nls.localizeByDefault('Select Version to Install'),
|
|
290
|
+
runIfSingle: false,
|
|
291
|
+
activeItem
|
|
292
|
+
});
|
|
293
|
+
if (selectedItem) {
|
|
294
|
+
const selectedExtension = this.model.getExtension(extensionId);
|
|
295
|
+
if (selectedExtension) {
|
|
296
|
+
await this.updateVersion(selectedExtension, selectedItem.label);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
protected async copy(extension: VSXExtension): Promise<void> {
|
|
302
|
+
this.clipboardService.writeText(await extension.serialize());
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
protected copyExtensionId(extension: VSXExtension): void {
|
|
306
|
+
this.clipboardService.writeText(extension.id);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Updates an extension to a specific version.
|
|
311
|
+
*
|
|
312
|
+
* @param extension the extension to update.
|
|
313
|
+
* @param updateToVersion the version to update to.
|
|
314
|
+
* @param revertToVersion the version to revert to (in case of failure).
|
|
315
|
+
*/
|
|
316
|
+
protected async updateVersion(extension: VSXExtension, updateToVersion: string): Promise<void> {
|
|
317
|
+
try {
|
|
318
|
+
await extension.install({ version: updateToVersion, ignoreOtherVersions: true });
|
|
319
|
+
} catch {
|
|
320
|
+
this.messageService.warn(nls.localize('theia/vsx-registry/vsx-extensions-contribution/update-version-version-error', 'Failed to install version {0} of {1}.',
|
|
321
|
+
updateToVersion, extension.displayName));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
if (extension.version !== updateToVersion) {
|
|
326
|
+
await extension.uninstall();
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
this.messageService.warn(nls.localize('theia/vsx-registry/vsx-extensions-contribution/update-version-uninstall-error', 'Error while removing the extension: {0}.',
|
|
330
|
+
extension.displayName));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
protected async showRecommendedToast(): Promise<void> {
|
|
335
|
+
if (!this.preferenceService.get(IGNORE_RECOMMENDATIONS_ID, false)) {
|
|
336
|
+
const recommended = new Set([...this.model.recommended]);
|
|
337
|
+
for (const installed of this.model.installed) {
|
|
338
|
+
recommended.delete(installed);
|
|
339
|
+
}
|
|
340
|
+
if (recommended.size) {
|
|
341
|
+
const install = nls.localizeByDefault('Install');
|
|
342
|
+
const showRecommendations = nls.localizeByDefault('Show Recommendations');
|
|
343
|
+
const userResponse = await this.messageService.info(
|
|
344
|
+
nls.localize('theia/vsx-registry/recommendedExtensions', 'Do you want to install the recommended extensions for this repository?'),
|
|
345
|
+
install,
|
|
346
|
+
showRecommendations
|
|
347
|
+
);
|
|
348
|
+
if (userResponse === install) {
|
|
349
|
+
for (const recommendation of recommended) {
|
|
350
|
+
this.model.getExtension(recommendation)?.install();
|
|
351
|
+
}
|
|
352
|
+
} else if (userResponse === showRecommendations) {
|
|
353
|
+
await this.showRecommendedExtensions();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
protected async showBuiltinExtensions(): Promise<void> {
|
|
360
|
+
await this.openView({ activate: true });
|
|
361
|
+
this.model.search.query = BUILTIN_QUERY;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
protected async showInstalledExtensions(): Promise<void> {
|
|
365
|
+
await this.openView({ activate: true });
|
|
366
|
+
this.model.search.query = INSTALLED_QUERY;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
protected async showRecommendedExtensions(): Promise<void> {
|
|
370
|
+
await this.openView({ activate: true });
|
|
371
|
+
this.model.search.query = RECOMMENDED_QUERY;
|
|
372
|
+
}
|
|
373
|
+
}
|