@theia/vsx-registry 1.34.2 → 1.34.3
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/LICENSE +641 -641
- package/README.md +36 -36
- package/lib/browser/recommended-extensions/preference-provider-overrides.d.ts +17 -17
- package/lib/browser/recommended-extensions/preference-provider-overrides.js +95 -95
- package/lib/browser/recommended-extensions/recommended-extensions-json-schema.d.ts +14 -14
- package/lib/browser/recommended-extensions/recommended-extensions-json-schema.js +94 -94
- package/lib/browser/recommended-extensions/recommended-extensions-preference-contribution.d.ts +11 -11
- package/lib/browser/recommended-extensions/recommended-extensions-preference-contribution.js +59 -59
- package/lib/browser/vsx-extension-commands.d.ts +13 -13
- package/lib/browser/vsx-extension-commands.js +63 -63
- package/lib/browser/vsx-extension-editor-manager.d.ts +9 -9
- package/lib/browser/vsx-extension-editor-manager.js +49 -49
- package/lib/browser/vsx-extension-editor.d.ts +21 -21
- package/lib/browser/vsx-extension-editor.js +109 -109
- package/lib/browser/vsx-extension.d.ts +141 -141
- package/lib/browser/vsx-extension.js +581 -581
- package/lib/browser/vsx-extensions-contribution.d.ts +54 -54
- package/lib/browser/vsx-extensions-contribution.js +328 -328
- package/lib/browser/vsx-extensions-model.d.ts +67 -67
- package/lib/browser/vsx-extensions-model.js +368 -368
- package/lib/browser/vsx-extensions-search-bar.d.ts +14 -14
- package/lib/browser/vsx-extensions-search-bar.js +75 -75
- package/lib/browser/vsx-extensions-search-model.d.ts +21 -21
- package/lib/browser/vsx-extensions-search-model.js +70 -70
- package/lib/browser/vsx-extensions-source.d.ts +19 -19
- package/lib/browser/vsx-extensions-source.js +102 -102
- package/lib/browser/vsx-extensions-view-container.d.ts +49 -49
- package/lib/browser/vsx-extensions-view-container.js +179 -179
- package/lib/browser/vsx-extensions-widget.d.ts +29 -29
- package/lib/browser/vsx-extensions-widget.js +143 -143
- package/lib/browser/vsx-language-quick-pick-service.d.ts +13 -13
- package/lib/browser/vsx-language-quick-pick-service.js +103 -103
- package/lib/browser/vsx-registry-frontend-module.d.ts +4 -4
- package/lib/browser/vsx-registry-frontend-module.js +101 -101
- package/lib/common/ovsx-client-provider.d.ts +6 -6
- package/lib/common/ovsx-client-provider.js +28 -28
- package/lib/common/vsx-environment.d.ts +7 -7
- package/lib/common/vsx-environment.js +20 -20
- package/lib/common/vsx-extension-uri.d.ts +3 -3
- package/lib/common/vsx-extension-uri.js +20 -20
- package/lib/node/vsx-environment-impl.d.ts +10 -10
- package/lib/node/vsx-environment-impl.js +53 -53
- package/lib/node/vsx-extension-resolver.d.ts +17 -17
- package/lib/node/vsx-extension-resolver.js +137 -137
- package/lib/node/vsx-registry-backend-module.d.ts +3 -3
- package/lib/node/vsx-registry-backend-module.js +35 -35
- package/lib/package.spec.js +25 -25
- package/package.json +10 -10
- 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 +354 -354
- package/src/browser/vsx-extension-commands.ts +63 -63
- 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 +662 -662
- package/src/browser/vsx-extensions-contribution.ts +315 -315
- package/src/browser/vsx-extensions-model.ts +366 -366
- package/src/browser/vsx-extensions-search-bar.tsx +69 -69
- 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 +138 -138
- package/src/browser/vsx-language-quick-pick-service.ts +97 -97
- package/src/browser/vsx-registry-frontend-module.ts +113 -113
- package/src/common/ovsx-client-provider.ts +30 -30
- package/src/common/vsx-environment.ts +24 -24
- package/src/common/vsx-extension-uri.ts +20 -20
- package/src/node/vsx-environment-impl.ts +41 -41
- package/src/node/vsx-extension-resolver.ts +111 -111
- package/src/node/vsx-registry-backend-module.ts +37 -37
- package/src/package.spec.ts +29 -29
|
@@ -1,315 +1,315 @@
|
|
|
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 WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import { DateTime } from 'luxon';
|
|
18
|
-
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
|
19
|
-
import debounce = require('@theia/core/shared/lodash.debounce');
|
|
20
|
-
import { CommandRegistry } from '@theia/core/lib/common/command';
|
|
21
|
-
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
|
22
|
-
import { VSXExtensionsViewContainer } from './vsx-extensions-view-container';
|
|
23
|
-
import { VSXExtensionsModel } from './vsx-extensions-model';
|
|
24
|
-
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
|
25
|
-
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
|
26
|
-
import { Color } from '@theia/core/lib/common/color';
|
|
27
|
-
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
|
28
|
-
import { MenuModelRegistry, MessageService, nls } from '@theia/core/lib/common';
|
|
29
|
-
import { FileDialogService, OpenFileDialogProps } from '@theia/filesystem/lib/browser';
|
|
30
|
-
import { LabelProvider, PreferenceService, QuickPickItem, QuickInputService } from '@theia/core/lib/browser';
|
|
31
|
-
import { VscodeCommands } from '@theia/plugin-ext-vscode/lib/browser/plugin-vscode-commands-contribution';
|
|
32
|
-
import { VSXExtensionsContextMenu, VSXExtension } from './vsx-extension';
|
|
33
|
-
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
|
34
|
-
import { BUILTIN_QUERY, INSTALLED_QUERY, RECOMMENDED_QUERY } from './vsx-extensions-search-model';
|
|
35
|
-
import { IGNORE_RECOMMENDATIONS_ID } from './recommended-extensions/recommended-extensions-preference-contribution';
|
|
36
|
-
import { VSXExtensionsCommands } from './vsx-extension-commands';
|
|
37
|
-
import { VSXExtensionRaw } from '@theia/ovsx-client';
|
|
38
|
-
import { OVSXClientProvider } from '../common/ovsx-client-provider';
|
|
39
|
-
|
|
40
|
-
@injectable()
|
|
41
|
-
export class VSXExtensionsContribution extends AbstractViewContribution<VSXExtensionsViewContainer>
|
|
42
|
-
implements ColorContribution, FrontendApplicationContribution {
|
|
43
|
-
|
|
44
|
-
@inject(VSXExtensionsModel) protected readonly model: VSXExtensionsModel;
|
|
45
|
-
@inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry;
|
|
46
|
-
@inject(FileDialogService) protected readonly fileDialogService: FileDialogService;
|
|
47
|
-
@inject(MessageService) protected readonly messageService: MessageService;
|
|
48
|
-
@inject(LabelProvider) protected readonly labelProvider: LabelProvider;
|
|
49
|
-
@inject(ClipboardService) protected readonly clipboardService: ClipboardService;
|
|
50
|
-
@inject(PreferenceService) protected readonly preferenceService: PreferenceService;
|
|
51
|
-
@inject(OVSXClientProvider) protected readonly clientProvider: OVSXClientProvider;
|
|
52
|
-
@inject(QuickInputService) protected readonly quickInput: QuickInputService;
|
|
53
|
-
|
|
54
|
-
constructor() {
|
|
55
|
-
super({
|
|
56
|
-
widgetId: VSXExtensionsViewContainer.ID,
|
|
57
|
-
widgetName: VSXExtensionsViewContainer.LABEL,
|
|
58
|
-
defaultWidgetOptions: {
|
|
59
|
-
area: 'left',
|
|
60
|
-
rank: 500
|
|
61
|
-
},
|
|
62
|
-
toggleCommandId: 'vsxExtensions.toggle',
|
|
63
|
-
toggleKeybinding: 'ctrlcmd+shift+x'
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
@postConstruct()
|
|
68
|
-
protected init(): void {
|
|
69
|
-
const oneShotDisposable = this.model.onDidChange(debounce(() => {
|
|
70
|
-
this.showRecommendedToast();
|
|
71
|
-
oneShotDisposable.dispose();
|
|
72
|
-
}, 5000, { trailing: true }));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async initializeLayout(app: FrontendApplication): Promise<void> {
|
|
76
|
-
await this.openView({ activate: false });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
override registerCommands(commands: CommandRegistry): void {
|
|
80
|
-
super.registerCommands(commands);
|
|
81
|
-
commands.registerCommand(VSXExtensionsCommands.CLEAR_ALL, {
|
|
82
|
-
execute: () => this.model.search.query = '',
|
|
83
|
-
isEnabled: () => !!this.model.search.query,
|
|
84
|
-
isVisible: () => true,
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
commands.registerCommand(VSXExtensionsCommands.INSTALL_FROM_VSIX, {
|
|
88
|
-
execute: () => this.installFromVSIX()
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
commands.registerCommand(VSXExtensionsCommands.INSTALL_ANOTHER_VERSION, {
|
|
92
|
-
// Check downloadUrl to ensure we have an idea of where to look for other versions.
|
|
93
|
-
isEnabled: (extension: VSXExtension) => !extension.builtin && !!extension.downloadUrl,
|
|
94
|
-
execute: async (extension: VSXExtension) => this.installAnotherVersion(extension),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
commands.registerCommand(VSXExtensionsCommands.COPY, {
|
|
98
|
-
execute: (extension: VSXExtension) => this.copy(extension)
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
commands.registerCommand(VSXExtensionsCommands.COPY_EXTENSION_ID, {
|
|
102
|
-
execute: (extension: VSXExtension) => this.copyExtensionId(extension)
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
commands.registerCommand(VSXExtensionsCommands.SHOW_BUILTINS, {
|
|
106
|
-
execute: () => this.showBuiltinExtensions()
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
commands.registerCommand(VSXExtensionsCommands.SHOW_INSTALLED, {
|
|
110
|
-
execute: () => this.showInstalledExtensions()
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
commands.registerCommand(VSXExtensionsCommands.SHOW_RECOMMENDATIONS, {
|
|
114
|
-
execute: () => this.showRecommendedExtensions()
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
override registerMenus(menus: MenuModelRegistry): void {
|
|
119
|
-
super.registerMenus(menus);
|
|
120
|
-
menus.registerMenuAction(VSXExtensionsContextMenu.COPY, {
|
|
121
|
-
commandId: VSXExtensionsCommands.COPY.id,
|
|
122
|
-
label: nls.localizeByDefault('Copy'),
|
|
123
|
-
order: '0'
|
|
124
|
-
});
|
|
125
|
-
menus.registerMenuAction(VSXExtensionsContextMenu.COPY, {
|
|
126
|
-
commandId: VSXExtensionsCommands.COPY_EXTENSION_ID.id,
|
|
127
|
-
label: nls.localizeByDefault('Copy Extension ID'),
|
|
128
|
-
order: '1'
|
|
129
|
-
});
|
|
130
|
-
menus.registerMenuAction(VSXExtensionsContextMenu.INSTALL, {
|
|
131
|
-
commandId: VSXExtensionsCommands.INSTALL_ANOTHER_VERSION.id,
|
|
132
|
-
label: nls.localizeByDefault('Install Another Version...'),
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
registerColors(colors: ColorRegistry): void {
|
|
137
|
-
// VS Code colors should be aligned with https://code.visualstudio.com/api/references/theme-color#extensions
|
|
138
|
-
colors.register(
|
|
139
|
-
{
|
|
140
|
-
id: 'extensionButton.prominentBackground', defaults: {
|
|
141
|
-
dark: '#327e36',
|
|
142
|
-
light: '#327e36'
|
|
143
|
-
}, description: 'Button background color for actions extension that stand out (e.g. install button).'
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
id: 'extensionButton.prominentForeground', defaults: {
|
|
147
|
-
dark: Color.white,
|
|
148
|
-
light: Color.white
|
|
149
|
-
}, description: 'Button foreground color for actions extension that stand out (e.g. install button).'
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
id: 'extensionButton.prominentHoverBackground', defaults: {
|
|
153
|
-
dark: '#28632b',
|
|
154
|
-
light: '#28632b'
|
|
155
|
-
}, description: 'Button background hover color for actions extension that stand out (e.g. install button).'
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
id: 'extensionEditor.tableHeadBorder', defaults: {
|
|
159
|
-
dark: Color.transparent('#ffffff', 0.7),
|
|
160
|
-
light: Color.transparent('#000000', 0.7),
|
|
161
|
-
hcDark: Color.white,
|
|
162
|
-
hcLight: Color.black
|
|
163
|
-
}, description: 'Border color for the table head row of the extension editor view'
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
id: 'extensionEditor.tableCellBorder', defaults: {
|
|
167
|
-
dark: Color.transparent('#ffffff', 0.2),
|
|
168
|
-
light: Color.transparent('#000000', 0.2),
|
|
169
|
-
hcDark: Color.white,
|
|
170
|
-
hcLight: Color.black
|
|
171
|
-
}, description: 'Border color for a table row of the extension editor view'
|
|
172
|
-
},
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Installs a local .vsix file after prompting the `Open File` dialog. Resolves to the URI of the file.
|
|
178
|
-
*/
|
|
179
|
-
protected async installFromVSIX(): Promise<void> {
|
|
180
|
-
const props: OpenFileDialogProps = {
|
|
181
|
-
title: VSXExtensionsCommands.INSTALL_FROM_VSIX.dialogLabel,
|
|
182
|
-
openLabel: nls.localizeByDefault('Install from VSIX'),
|
|
183
|
-
filters: { 'VSIX Extensions (*.vsix)': ['vsix'] },
|
|
184
|
-
canSelectMany: false
|
|
185
|
-
};
|
|
186
|
-
const extensionUri = await this.fileDialogService.showOpenDialog(props);
|
|
187
|
-
if (extensionUri) {
|
|
188
|
-
if (extensionUri.path.ext === '.vsix') {
|
|
189
|
-
const extensionName = this.labelProvider.getName(extensionUri);
|
|
190
|
-
try {
|
|
191
|
-
await this.commandRegistry.executeCommand(VscodeCommands.INSTALL_FROM_VSIX.id, extensionUri);
|
|
192
|
-
this.messageService.info(nls.localizeByDefault('Completed installing {0} extension from VSIX.', extensionName));
|
|
193
|
-
} catch (e) {
|
|
194
|
-
this.messageService.error(nls.localize('theia/vsx-registry/failedInstallingVSIX', 'Failed to install {0} from VSIX.', extensionName));
|
|
195
|
-
console.warn(e);
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
this.messageService.error(nls.localize('theia/vsx-registry/invalidVSIX', 'The selected file is not a valid "*.vsix" plugin.'));
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Given an extension, displays a quick pick of other compatible versions and installs the selected version.
|
|
205
|
-
*
|
|
206
|
-
* @param extension a VSX extension.
|
|
207
|
-
*/
|
|
208
|
-
protected async installAnotherVersion(extension: VSXExtension): Promise<void> {
|
|
209
|
-
const extensionId = extension.id;
|
|
210
|
-
const currentVersion = extension.version;
|
|
211
|
-
const client = await this.clientProvider();
|
|
212
|
-
const extensions = await client.getAllVersions(extensionId);
|
|
213
|
-
const latestCompatible = await client.getLatestCompatibleExtensionVersion(extensionId);
|
|
214
|
-
let compatibleExtensions: VSXExtensionRaw[] = [];
|
|
215
|
-
let activeItem = undefined;
|
|
216
|
-
if (latestCompatible) {
|
|
217
|
-
compatibleExtensions = extensions.slice(extensions.findIndex(ext => ext.version === latestCompatible.version));
|
|
218
|
-
}
|
|
219
|
-
const items: QuickPickItem[] = compatibleExtensions.map(ext => {
|
|
220
|
-
const item = {
|
|
221
|
-
label: ext.version,
|
|
222
|
-
description: DateTime.fromISO(ext.timestamp).toRelative({ locale: nls.locale }) ?? ''
|
|
223
|
-
};
|
|
224
|
-
if (currentVersion === ext.version) {
|
|
225
|
-
item.description += ` (${nls.localizeByDefault('Current')})`;
|
|
226
|
-
activeItem = item;
|
|
227
|
-
}
|
|
228
|
-
return item;
|
|
229
|
-
});
|
|
230
|
-
const selectedItem = await this.quickInput.showQuickPick(items, {
|
|
231
|
-
placeholder: nls.localizeByDefault('Select Version to Install'),
|
|
232
|
-
runIfSingle: false,
|
|
233
|
-
activeItem
|
|
234
|
-
});
|
|
235
|
-
if (selectedItem) {
|
|
236
|
-
const selectedExtension = this.model.getExtension(extensionId);
|
|
237
|
-
if (selectedExtension) {
|
|
238
|
-
await this.updateVersion(selectedExtension, selectedItem.label);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
protected async copy(extension: VSXExtension): Promise<void> {
|
|
244
|
-
this.clipboardService.writeText(await extension.serialize());
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
protected copyExtensionId(extension: VSXExtension): void {
|
|
248
|
-
this.clipboardService.writeText(extension.id);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Updates an extension to a specific version.
|
|
253
|
-
*
|
|
254
|
-
* @param extension the extension to update.
|
|
255
|
-
* @param updateToVersion the version to update to.
|
|
256
|
-
* @param revertToVersion the version to revert to (in case of failure).
|
|
257
|
-
*/
|
|
258
|
-
protected async updateVersion(extension: VSXExtension, updateToVersion: string): Promise<void> {
|
|
259
|
-
try {
|
|
260
|
-
await extension.install({ version: updateToVersion, ignoreOtherVersions: true });
|
|
261
|
-
} catch {
|
|
262
|
-
this.messageService.warn(nls.localize('theia/vsx-registry/vsx-extensions-contribution/update-version-version-error', 'Failed to install version {0} of {1}.',
|
|
263
|
-
updateToVersion, extension.displayName));
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
try {
|
|
267
|
-
if (extension.version !== updateToVersion) {
|
|
268
|
-
await extension.uninstall();
|
|
269
|
-
}
|
|
270
|
-
} catch {
|
|
271
|
-
this.messageService.warn(nls.localize('theia/vsx-registry/vsx-extensions-contribution/update-version-uninstall-error', 'Error while removing the extension: {0}.',
|
|
272
|
-
extension.displayName));
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
protected async showRecommendedToast(): Promise<void> {
|
|
277
|
-
if (!this.preferenceService.get(IGNORE_RECOMMENDATIONS_ID, false)) {
|
|
278
|
-
const recommended = new Set([...this.model.recommended]);
|
|
279
|
-
for (const installed of this.model.installed) {
|
|
280
|
-
recommended.delete(installed);
|
|
281
|
-
}
|
|
282
|
-
if (recommended.size) {
|
|
283
|
-
const install = nls.localizeByDefault('Install');
|
|
284
|
-
const showRecommendations = nls.localizeByDefault('Show Recommendations');
|
|
285
|
-
const userResponse = await this.messageService.info(
|
|
286
|
-
nls.localizeByDefault('Do you want to install the recommended extensions for this repository?'),
|
|
287
|
-
install,
|
|
288
|
-
showRecommendations
|
|
289
|
-
);
|
|
290
|
-
if (userResponse === install) {
|
|
291
|
-
for (const recommendation of recommended) {
|
|
292
|
-
this.model.getExtension(recommendation)?.install();
|
|
293
|
-
}
|
|
294
|
-
} else if (userResponse === showRecommendations) {
|
|
295
|
-
await this.showRecommendedExtensions();
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
protected async showBuiltinExtensions(): Promise<void> {
|
|
302
|
-
await this.openView({ activate: true });
|
|
303
|
-
this.model.search.query = BUILTIN_QUERY;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
protected async showInstalledExtensions(): Promise<void> {
|
|
307
|
-
await this.openView({ activate: true });
|
|
308
|
-
this.model.search.query = INSTALLED_QUERY;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
protected async showRecommendedExtensions(): Promise<void> {
|
|
312
|
-
await this.openView({ activate: true });
|
|
313
|
-
this.model.search.query = RECOMMENDED_QUERY;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
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 WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { DateTime } from 'luxon';
|
|
18
|
+
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
|
19
|
+
import debounce = require('@theia/core/shared/lodash.debounce');
|
|
20
|
+
import { CommandRegistry } from '@theia/core/lib/common/command';
|
|
21
|
+
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
|
22
|
+
import { VSXExtensionsViewContainer } from './vsx-extensions-view-container';
|
|
23
|
+
import { VSXExtensionsModel } from './vsx-extensions-model';
|
|
24
|
+
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
|
25
|
+
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
|
26
|
+
import { Color } from '@theia/core/lib/common/color';
|
|
27
|
+
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
|
28
|
+
import { MenuModelRegistry, MessageService, nls } from '@theia/core/lib/common';
|
|
29
|
+
import { FileDialogService, OpenFileDialogProps } from '@theia/filesystem/lib/browser';
|
|
30
|
+
import { LabelProvider, PreferenceService, QuickPickItem, QuickInputService } from '@theia/core/lib/browser';
|
|
31
|
+
import { VscodeCommands } from '@theia/plugin-ext-vscode/lib/browser/plugin-vscode-commands-contribution';
|
|
32
|
+
import { VSXExtensionsContextMenu, VSXExtension } from './vsx-extension';
|
|
33
|
+
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
|
34
|
+
import { BUILTIN_QUERY, INSTALLED_QUERY, RECOMMENDED_QUERY } from './vsx-extensions-search-model';
|
|
35
|
+
import { IGNORE_RECOMMENDATIONS_ID } from './recommended-extensions/recommended-extensions-preference-contribution';
|
|
36
|
+
import { VSXExtensionsCommands } from './vsx-extension-commands';
|
|
37
|
+
import { VSXExtensionRaw } from '@theia/ovsx-client';
|
|
38
|
+
import { OVSXClientProvider } from '../common/ovsx-client-provider';
|
|
39
|
+
|
|
40
|
+
@injectable()
|
|
41
|
+
export class VSXExtensionsContribution extends AbstractViewContribution<VSXExtensionsViewContainer>
|
|
42
|
+
implements ColorContribution, FrontendApplicationContribution {
|
|
43
|
+
|
|
44
|
+
@inject(VSXExtensionsModel) protected readonly model: VSXExtensionsModel;
|
|
45
|
+
@inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry;
|
|
46
|
+
@inject(FileDialogService) protected readonly fileDialogService: FileDialogService;
|
|
47
|
+
@inject(MessageService) protected readonly messageService: MessageService;
|
|
48
|
+
@inject(LabelProvider) protected readonly labelProvider: LabelProvider;
|
|
49
|
+
@inject(ClipboardService) protected readonly clipboardService: ClipboardService;
|
|
50
|
+
@inject(PreferenceService) protected readonly preferenceService: PreferenceService;
|
|
51
|
+
@inject(OVSXClientProvider) protected readonly clientProvider: OVSXClientProvider;
|
|
52
|
+
@inject(QuickInputService) protected readonly quickInput: QuickInputService;
|
|
53
|
+
|
|
54
|
+
constructor() {
|
|
55
|
+
super({
|
|
56
|
+
widgetId: VSXExtensionsViewContainer.ID,
|
|
57
|
+
widgetName: VSXExtensionsViewContainer.LABEL,
|
|
58
|
+
defaultWidgetOptions: {
|
|
59
|
+
area: 'left',
|
|
60
|
+
rank: 500
|
|
61
|
+
},
|
|
62
|
+
toggleCommandId: 'vsxExtensions.toggle',
|
|
63
|
+
toggleKeybinding: 'ctrlcmd+shift+x'
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@postConstruct()
|
|
68
|
+
protected init(): void {
|
|
69
|
+
const oneShotDisposable = this.model.onDidChange(debounce(() => {
|
|
70
|
+
this.showRecommendedToast();
|
|
71
|
+
oneShotDisposable.dispose();
|
|
72
|
+
}, 5000, { trailing: true }));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async initializeLayout(app: FrontendApplication): Promise<void> {
|
|
76
|
+
await this.openView({ activate: false });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override registerCommands(commands: CommandRegistry): void {
|
|
80
|
+
super.registerCommands(commands);
|
|
81
|
+
commands.registerCommand(VSXExtensionsCommands.CLEAR_ALL, {
|
|
82
|
+
execute: () => this.model.search.query = '',
|
|
83
|
+
isEnabled: () => !!this.model.search.query,
|
|
84
|
+
isVisible: () => true,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
commands.registerCommand(VSXExtensionsCommands.INSTALL_FROM_VSIX, {
|
|
88
|
+
execute: () => this.installFromVSIX()
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
commands.registerCommand(VSXExtensionsCommands.INSTALL_ANOTHER_VERSION, {
|
|
92
|
+
// Check downloadUrl to ensure we have an idea of where to look for other versions.
|
|
93
|
+
isEnabled: (extension: VSXExtension) => !extension.builtin && !!extension.downloadUrl,
|
|
94
|
+
execute: async (extension: VSXExtension) => this.installAnotherVersion(extension),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
commands.registerCommand(VSXExtensionsCommands.COPY, {
|
|
98
|
+
execute: (extension: VSXExtension) => this.copy(extension)
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
commands.registerCommand(VSXExtensionsCommands.COPY_EXTENSION_ID, {
|
|
102
|
+
execute: (extension: VSXExtension) => this.copyExtensionId(extension)
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
commands.registerCommand(VSXExtensionsCommands.SHOW_BUILTINS, {
|
|
106
|
+
execute: () => this.showBuiltinExtensions()
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
commands.registerCommand(VSXExtensionsCommands.SHOW_INSTALLED, {
|
|
110
|
+
execute: () => this.showInstalledExtensions()
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
commands.registerCommand(VSXExtensionsCommands.SHOW_RECOMMENDATIONS, {
|
|
114
|
+
execute: () => this.showRecommendedExtensions()
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
override registerMenus(menus: MenuModelRegistry): void {
|
|
119
|
+
super.registerMenus(menus);
|
|
120
|
+
menus.registerMenuAction(VSXExtensionsContextMenu.COPY, {
|
|
121
|
+
commandId: VSXExtensionsCommands.COPY.id,
|
|
122
|
+
label: nls.localizeByDefault('Copy'),
|
|
123
|
+
order: '0'
|
|
124
|
+
});
|
|
125
|
+
menus.registerMenuAction(VSXExtensionsContextMenu.COPY, {
|
|
126
|
+
commandId: VSXExtensionsCommands.COPY_EXTENSION_ID.id,
|
|
127
|
+
label: nls.localizeByDefault('Copy Extension ID'),
|
|
128
|
+
order: '1'
|
|
129
|
+
});
|
|
130
|
+
menus.registerMenuAction(VSXExtensionsContextMenu.INSTALL, {
|
|
131
|
+
commandId: VSXExtensionsCommands.INSTALL_ANOTHER_VERSION.id,
|
|
132
|
+
label: nls.localizeByDefault('Install Another Version...'),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
registerColors(colors: ColorRegistry): void {
|
|
137
|
+
// VS Code colors should be aligned with https://code.visualstudio.com/api/references/theme-color#extensions
|
|
138
|
+
colors.register(
|
|
139
|
+
{
|
|
140
|
+
id: 'extensionButton.prominentBackground', defaults: {
|
|
141
|
+
dark: '#327e36',
|
|
142
|
+
light: '#327e36'
|
|
143
|
+
}, description: 'Button background color for actions extension that stand out (e.g. install button).'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'extensionButton.prominentForeground', defaults: {
|
|
147
|
+
dark: Color.white,
|
|
148
|
+
light: Color.white
|
|
149
|
+
}, description: 'Button foreground color for actions extension that stand out (e.g. install button).'
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'extensionButton.prominentHoverBackground', defaults: {
|
|
153
|
+
dark: '#28632b',
|
|
154
|
+
light: '#28632b'
|
|
155
|
+
}, description: 'Button background hover color for actions extension that stand out (e.g. install button).'
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: 'extensionEditor.tableHeadBorder', defaults: {
|
|
159
|
+
dark: Color.transparent('#ffffff', 0.7),
|
|
160
|
+
light: Color.transparent('#000000', 0.7),
|
|
161
|
+
hcDark: Color.white,
|
|
162
|
+
hcLight: Color.black
|
|
163
|
+
}, description: 'Border color for the table head row of the extension editor view'
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'extensionEditor.tableCellBorder', defaults: {
|
|
167
|
+
dark: Color.transparent('#ffffff', 0.2),
|
|
168
|
+
light: Color.transparent('#000000', 0.2),
|
|
169
|
+
hcDark: Color.white,
|
|
170
|
+
hcLight: Color.black
|
|
171
|
+
}, description: 'Border color for a table row of the extension editor view'
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Installs a local .vsix file after prompting the `Open File` dialog. Resolves to the URI of the file.
|
|
178
|
+
*/
|
|
179
|
+
protected async installFromVSIX(): Promise<void> {
|
|
180
|
+
const props: OpenFileDialogProps = {
|
|
181
|
+
title: VSXExtensionsCommands.INSTALL_FROM_VSIX.dialogLabel,
|
|
182
|
+
openLabel: nls.localizeByDefault('Install from VSIX'),
|
|
183
|
+
filters: { 'VSIX Extensions (*.vsix)': ['vsix'] },
|
|
184
|
+
canSelectMany: false
|
|
185
|
+
};
|
|
186
|
+
const extensionUri = await this.fileDialogService.showOpenDialog(props);
|
|
187
|
+
if (extensionUri) {
|
|
188
|
+
if (extensionUri.path.ext === '.vsix') {
|
|
189
|
+
const extensionName = this.labelProvider.getName(extensionUri);
|
|
190
|
+
try {
|
|
191
|
+
await this.commandRegistry.executeCommand(VscodeCommands.INSTALL_FROM_VSIX.id, extensionUri);
|
|
192
|
+
this.messageService.info(nls.localizeByDefault('Completed installing {0} extension from VSIX.', extensionName));
|
|
193
|
+
} catch (e) {
|
|
194
|
+
this.messageService.error(nls.localize('theia/vsx-registry/failedInstallingVSIX', 'Failed to install {0} from VSIX.', extensionName));
|
|
195
|
+
console.warn(e);
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
this.messageService.error(nls.localize('theia/vsx-registry/invalidVSIX', 'The selected file is not a valid "*.vsix" plugin.'));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Given an extension, displays a quick pick of other compatible versions and installs the selected version.
|
|
205
|
+
*
|
|
206
|
+
* @param extension a VSX extension.
|
|
207
|
+
*/
|
|
208
|
+
protected async installAnotherVersion(extension: VSXExtension): Promise<void> {
|
|
209
|
+
const extensionId = extension.id;
|
|
210
|
+
const currentVersion = extension.version;
|
|
211
|
+
const client = await this.clientProvider();
|
|
212
|
+
const extensions = await client.getAllVersions(extensionId);
|
|
213
|
+
const latestCompatible = await client.getLatestCompatibleExtensionVersion(extensionId);
|
|
214
|
+
let compatibleExtensions: VSXExtensionRaw[] = [];
|
|
215
|
+
let activeItem = undefined;
|
|
216
|
+
if (latestCompatible) {
|
|
217
|
+
compatibleExtensions = extensions.slice(extensions.findIndex(ext => ext.version === latestCompatible.version));
|
|
218
|
+
}
|
|
219
|
+
const items: QuickPickItem[] = compatibleExtensions.map(ext => {
|
|
220
|
+
const item = {
|
|
221
|
+
label: ext.version,
|
|
222
|
+
description: DateTime.fromISO(ext.timestamp).toRelative({ locale: nls.locale }) ?? ''
|
|
223
|
+
};
|
|
224
|
+
if (currentVersion === ext.version) {
|
|
225
|
+
item.description += ` (${nls.localizeByDefault('Current')})`;
|
|
226
|
+
activeItem = item;
|
|
227
|
+
}
|
|
228
|
+
return item;
|
|
229
|
+
});
|
|
230
|
+
const selectedItem = await this.quickInput.showQuickPick(items, {
|
|
231
|
+
placeholder: nls.localizeByDefault('Select Version to Install'),
|
|
232
|
+
runIfSingle: false,
|
|
233
|
+
activeItem
|
|
234
|
+
});
|
|
235
|
+
if (selectedItem) {
|
|
236
|
+
const selectedExtension = this.model.getExtension(extensionId);
|
|
237
|
+
if (selectedExtension) {
|
|
238
|
+
await this.updateVersion(selectedExtension, selectedItem.label);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
protected async copy(extension: VSXExtension): Promise<void> {
|
|
244
|
+
this.clipboardService.writeText(await extension.serialize());
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
protected copyExtensionId(extension: VSXExtension): void {
|
|
248
|
+
this.clipboardService.writeText(extension.id);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Updates an extension to a specific version.
|
|
253
|
+
*
|
|
254
|
+
* @param extension the extension to update.
|
|
255
|
+
* @param updateToVersion the version to update to.
|
|
256
|
+
* @param revertToVersion the version to revert to (in case of failure).
|
|
257
|
+
*/
|
|
258
|
+
protected async updateVersion(extension: VSXExtension, updateToVersion: string): Promise<void> {
|
|
259
|
+
try {
|
|
260
|
+
await extension.install({ version: updateToVersion, ignoreOtherVersions: true });
|
|
261
|
+
} catch {
|
|
262
|
+
this.messageService.warn(nls.localize('theia/vsx-registry/vsx-extensions-contribution/update-version-version-error', 'Failed to install version {0} of {1}.',
|
|
263
|
+
updateToVersion, extension.displayName));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
if (extension.version !== updateToVersion) {
|
|
268
|
+
await extension.uninstall();
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
this.messageService.warn(nls.localize('theia/vsx-registry/vsx-extensions-contribution/update-version-uninstall-error', 'Error while removing the extension: {0}.',
|
|
272
|
+
extension.displayName));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
protected async showRecommendedToast(): Promise<void> {
|
|
277
|
+
if (!this.preferenceService.get(IGNORE_RECOMMENDATIONS_ID, false)) {
|
|
278
|
+
const recommended = new Set([...this.model.recommended]);
|
|
279
|
+
for (const installed of this.model.installed) {
|
|
280
|
+
recommended.delete(installed);
|
|
281
|
+
}
|
|
282
|
+
if (recommended.size) {
|
|
283
|
+
const install = nls.localizeByDefault('Install');
|
|
284
|
+
const showRecommendations = nls.localizeByDefault('Show Recommendations');
|
|
285
|
+
const userResponse = await this.messageService.info(
|
|
286
|
+
nls.localizeByDefault('Do you want to install the recommended extensions for this repository?'),
|
|
287
|
+
install,
|
|
288
|
+
showRecommendations
|
|
289
|
+
);
|
|
290
|
+
if (userResponse === install) {
|
|
291
|
+
for (const recommendation of recommended) {
|
|
292
|
+
this.model.getExtension(recommendation)?.install();
|
|
293
|
+
}
|
|
294
|
+
} else if (userResponse === showRecommendations) {
|
|
295
|
+
await this.showRecommendedExtensions();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
protected async showBuiltinExtensions(): Promise<void> {
|
|
302
|
+
await this.openView({ activate: true });
|
|
303
|
+
this.model.search.query = BUILTIN_QUERY;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
protected async showInstalledExtensions(): Promise<void> {
|
|
307
|
+
await this.openView({ activate: true });
|
|
308
|
+
this.model.search.query = INSTALLED_QUERY;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
protected async showRecommendedExtensions(): Promise<void> {
|
|
312
|
+
await this.openView({ activate: true });
|
|
313
|
+
this.model.search.query = RECOMMENDED_QUERY;
|
|
314
|
+
}
|
|
315
|
+
}
|