@theia/vsx-registry 1.45.0 → 1.46.0-next.72

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