@theia/vsx-registry 1.60.2 → 1.61.0

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 (29) hide show
  1. package/README.md +1 -1
  2. package/lib/browser/vsx-extension-commands.d.ts +2 -0
  3. package/lib/browser/vsx-extension-commands.d.ts.map +1 -1
  4. package/lib/browser/vsx-extension-commands.js +6 -0
  5. package/lib/browser/vsx-extension-commands.js.map +1 -1
  6. package/lib/browser/vsx-extension-editor-manager.d.ts +3 -2
  7. package/lib/browser/vsx-extension-editor-manager.d.ts.map +1 -1
  8. package/lib/browser/vsx-extension-editor-manager.js.map +1 -1
  9. package/lib/browser/vsx-extension.d.ts +16 -2
  10. package/lib/browser/vsx-extension.d.ts.map +1 -1
  11. package/lib/browser/vsx-extension.js +80 -49
  12. package/lib/browser/vsx-extension.js.map +1 -1
  13. package/lib/browser/vsx-extensions-contribution.d.ts.map +1 -1
  14. package/lib/browser/vsx-extensions-contribution.js +18 -0
  15. package/lib/browser/vsx-extensions-contribution.js.map +1 -1
  16. package/lib/browser/vsx-extensions-model.d.ts +14 -2
  17. package/lib/browser/vsx-extensions-model.d.ts.map +1 -1
  18. package/lib/browser/vsx-extensions-model.js +88 -19
  19. package/lib/browser/vsx-extensions-model.js.map +1 -1
  20. package/lib/browser/vsx-language-quick-pick-service.js +1 -1
  21. package/lib/browser/vsx-language-quick-pick-service.js.map +1 -1
  22. package/package.json +11 -11
  23. package/src/browser/style/index.css +27 -41
  24. package/src/browser/vsx-extension-commands.ts +8 -0
  25. package/src/browser/vsx-extension-editor-manager.ts +1 -2
  26. package/src/browser/vsx-extension.tsx +103 -52
  27. package/src/browser/vsx-extensions-contribution.ts +21 -0
  28. package/src/browser/vsx-extensions-model.ts +95 -23
  29. package/src/browser/vsx-language-quick-pick-service.ts +1 -1
@@ -21,7 +21,7 @@ import URI from '@theia/core/lib/common/uri';
21
21
  import { TreeElement, TreeElementNode } from '@theia/core/lib/browser/source-tree';
22
22
  import { OpenerService, open, OpenerOptions } from '@theia/core/lib/browser/opener-service';
23
23
  import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
24
- import { PluginServer, DeployedPlugin, PluginType, PluginIdentifiers, PluginDeployOptions } from '@theia/plugin-ext/lib/common/plugin-protocol';
24
+ import { PluginServer, DeployedPlugin, PluginIdentifiers, PluginDeployOptions } from '@theia/plugin-ext/lib/common/plugin-protocol';
25
25
  import { VSCodeExtensionUri } from '@theia/plugin-ext-vscode/lib/common/plugin-vscode-uri';
26
26
  import { ProgressService } from '@theia/core/lib/common/progress-service';
27
27
  import { Endpoint } from '@theia/core/lib/browser/endpoint';
@@ -32,13 +32,16 @@ import { codicon, ConfirmDialog, ContextMenuRenderer, HoverService, TreeWidget }
32
32
  import { VSXExtensionNamespaceAccess, VSXUser } from '@theia/ovsx-client/lib/ovsx-types';
33
33
  import { WindowService } from '@theia/core/lib/browser/window/window-service';
34
34
  import { MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering';
35
+ import { VSXExtensionsModel } from './vsx-extensions-model';
35
36
 
36
37
  export const EXTENSIONS_CONTEXT_MENU: MenuPath = ['extensions_context_menu'];
37
38
 
38
39
  export namespace VSXExtensionsContextMenu {
39
40
  export const INSTALL = [...EXTENSIONS_CONTEXT_MENU, '1_install'];
40
- export const COPY = [...EXTENSIONS_CONTEXT_MENU, '2_copy'];
41
- export const CONTRIBUTION = [...EXTENSIONS_CONTEXT_MENU, '3_contribution'];
41
+ export const DISABLE = [...EXTENSIONS_CONTEXT_MENU, '2_disable'];
42
+ export const ENABLE = [...EXTENSIONS_CONTEXT_MENU, '2_enable'];
43
+ export const COPY = [...EXTENSIONS_CONTEXT_MENU, '3_copy'];
44
+ export const CONTRIBUTION = [...EXTENSIONS_CONTEXT_MENU, '4_contribution'];
42
45
  }
43
46
 
44
47
  @injectable()
@@ -86,6 +89,8 @@ export class VSXExtensionData {
86
89
  @injectable()
87
90
  export class VSXExtensionOptions {
88
91
  readonly id: string;
92
+ readonly version?: string;
93
+ readonly model: VSXExtensionsModel;
89
94
  }
90
95
 
91
96
  export const VSXExtensionFactory = Symbol('VSXExtensionFactory');
@@ -153,8 +158,16 @@ export class VSXExtension implements VSXExtensionData, TreeElement {
153
158
  return this.options.id;
154
159
  }
155
160
 
161
+ get installedVersion(): string | undefined {
162
+ return this.plugin?.metadata.model.version || this.options.version;
163
+ }
164
+
165
+ get model(): VSXExtensionsModel {
166
+ return this.options.model;
167
+ }
168
+
156
169
  get visible(): boolean {
157
- return !!this.name;
170
+ return true;
158
171
  }
159
172
 
160
173
  get plugin(): DeployedPlugin | undefined {
@@ -162,11 +175,23 @@ export class VSXExtension implements VSXExtensionData, TreeElement {
162
175
  }
163
176
 
164
177
  get installed(): boolean {
165
- return !!this.plugin;
178
+ return this.model.isInstalled(this.id);
179
+ }
180
+
181
+ get uninstalled(): boolean {
182
+ return this.model.isUninstalled(this.id);
183
+ }
184
+
185
+ get deployed(): boolean {
186
+ return this.model.isDeployed(this.id);
187
+ }
188
+
189
+ get disabled(): boolean {
190
+ return this.model.isDisabled(this.id);
166
191
  }
167
192
 
168
193
  get builtin(): boolean {
169
- return this.plugin?.type === PluginType.System;
194
+ return this.model.isBuiltIn(this.id);
170
195
  }
171
196
 
172
197
  update(data: Partial<VSXExtensionData>): void {
@@ -298,9 +323,23 @@ export class VSXExtension implements VSXExtensionData, TreeElement {
298
323
  return md;
299
324
  }
300
325
 
301
- protected _busy = 0;
302
- get busy(): boolean {
303
- return !!this._busy;
326
+ protected _currentTaskName: string | undefined;
327
+ get currentTask(): string | undefined {
328
+ return this._currentTaskName;
329
+ }
330
+ protected _currentTask: Promise<void> | undefined;
331
+
332
+ protected runTask(name: string, task: () => Promise<void>): Promise<void> {
333
+ if (this._currentTask) {
334
+ return Promise.reject('busy');
335
+ }
336
+ this._currentTaskName = name;
337
+ this._currentTask = task();
338
+ this._currentTask.finally(() => {
339
+ this._currentTask = undefined;
340
+ this._currentTaskName = undefined;
341
+ });
342
+ return this._currentTask;
304
343
  }
305
344
 
306
345
  async install(options?: PluginDeployOptions): Promise<void> {
@@ -310,39 +349,56 @@ export class VSXExtension implements VSXExtensionData, TreeElement {
310
349
  msg: nls.localize('theia/vsx-registry/confirmDialogMessage', 'The extension "{0}" is unverified and might pose a security risk.', this.displayName)
311
350
  }).open();
312
351
  if (choice) {
313
- this.doInstall(options);
352
+ await this.doInstall(options);
314
353
  }
315
354
  } else {
316
- this.doInstall(options);
355
+ await this.doInstall(options);
317
356
  }
318
357
  }
319
358
 
320
359
  async uninstall(): Promise<void> {
321
- this._busy++;
322
- try {
323
- const { plugin } = this;
324
- if (plugin) {
325
- await this.progressService.withProgress(
360
+ const { id, installedVersion } = this;
361
+ if (id && installedVersion) {
362
+ await this.runTask(nls.localizeByDefault('Uninstalling'),
363
+ async () => await this.progressService.withProgress(
326
364
  nls.localizeByDefault('Uninstalling {0}...', this.id), 'extensions',
327
- () => this.pluginServer.uninstall(PluginIdentifiers.componentsToVersionedId(plugin.metadata.model))
365
+ () => this.pluginServer.uninstall(PluginIdentifiers.idAndVersionToVersionedId({ id: (id as PluginIdentifiers.UnversionedId), version: installedVersion }))
366
+ )
367
+ );
368
+ }
369
+ }
370
+
371
+ async disable(): Promise<void> {
372
+ const { id, installedVersion } = this;
373
+ if (id && installedVersion) {
374
+ await this.runTask(nls.localize('vsx.disabling', 'Disabling'), async () => {
375
+ await this.progressService.withProgress(
376
+ nls.localize('vsx.disabling.extensions', 'Disabling {0}...', this.id), 'extensions',
377
+ () => this.pluginServer.disablePlugin(PluginIdentifiers.idAndVersionToVersionedId({ id: (id as PluginIdentifiers.UnversionedId), version: installedVersion }))
328
378
  );
329
- }
330
- } finally {
331
- this._busy--;
379
+ });
332
380
  }
333
381
  }
334
382
 
335
- protected async doInstall(options?: PluginDeployOptions): Promise<void> {
336
- this._busy++;
337
- try {
338
- await this.progressService.withProgress(nls.localizeByDefault("Installing extension '{0}' v{1}...", this.id, this.version ?? 0), 'extensions', () =>
339
- this.pluginServer.deploy(this.uri.toString(), undefined, options)
340
- );
341
- } finally {
342
- this._busy--;
383
+ async enable(): Promise<void> {
384
+ const { id, installedVersion } = this;
385
+ if (id && installedVersion) {
386
+ await this.runTask(nls.localize('vsx.enabling', 'Enabling'), async () => {
387
+ await this.progressService.withProgress(
388
+ nls.localize('vsx.enabling.extension', 'Enabling {0}...', this.id), 'extensions',
389
+ () => this.pluginServer.enablePlugin(PluginIdentifiers.idAndVersionToVersionedId({ id: (id as PluginIdentifiers.UnversionedId), version: installedVersion }))
390
+ );
391
+ });
343
392
  }
344
393
  }
345
394
 
395
+ protected async doInstall(options?: PluginDeployOptions): Promise<void> {
396
+ await this.runTask(nls.localizeByDefault('Installing'),
397
+ () => this.progressService.withProgress(nls.localizeByDefault("Installing extension '{0}' v{1}...", this.id, this.version ?? 0), 'extensions', () =>
398
+ this.pluginServer.install(this.uri.toString(), undefined, options)
399
+ ));
400
+ }
401
+
346
402
  handleContextMenu(e: React.MouseEvent<HTMLElement, MouseEvent>): void {
347
403
  e.preventDefault();
348
404
  this.contextMenuRenderer.render({
@@ -435,32 +491,24 @@ export abstract class AbstractVSXExtensionComponent<Props extends AbstractVSXExt
435
491
  };
436
492
 
437
493
  protected renderAction(host?: TreeWidget): React.ReactNode {
438
- const { builtin, busy, plugin } = this.props.extension;
494
+ const { builtin, currentTask, disabled, uninstalled, installed, deployed } = this.props.extension;
439
495
  const isFocused = (host?.model.getFocusedNode() as TreeElementNode)?.element === this.props.extension;
440
496
  const tabIndex = (!host || isFocused) ? 0 : undefined;
441
- const installed = !!plugin;
442
- const outOfSynch = plugin?.metadata.outOfSync;
443
- if (builtin) {
444
- return <div className="codicon codicon-settings-gear action" tabIndex={tabIndex} onClick={this.manage}></div>;
497
+ const outOfSync = installed && (deployed ? (disabled || uninstalled) : !(disabled || uninstalled));
498
+ if (currentTask) {
499
+ return <button className="theia-button action prominent theia-mod-disabled">{currentTask}</button>;
445
500
  }
446
- if (busy) {
447
- if (installed) {
448
- return <button className="theia-button action theia-mod-disabled">{nls.localizeByDefault('Uninstalling')}</button>;
501
+ return <div>
502
+ {
503
+ outOfSync && <button className="theia-button action" onClick={this.reloadWindow}>{nls.localizeByDefault('Reload Window')}</button>
449
504
  }
450
- return <button className="theia-button action prominent theia-mod-disabled">{nls.localizeByDefault('Installing')}</button>;
451
- }
452
- if (installed) {
453
- return <div>
454
- {
455
- outOfSynch
456
- ? <button className="theia-button action" onClick={this.reloadWindow}>{nls.localizeByDefault('Reload Window')}</button>
457
- : <button className="theia-button action" onClick={this.uninstall}>{nls.localizeByDefault('Uninstall')}</button>
458
- }
459
-
460
- <div className="codicon codicon-settings-gear action" onClick={this.manage}></div>
461
- </div>;
462
- }
463
- return <button className="theia-button prominent action" onClick={this.install}>{nls.localizeByDefault('Install')}</button>;
505
+ {
506
+ !builtin && ((installed && !uninstalled) ?
507
+ <button className="theia-button action" onClick={this.uninstall}>{nls.localizeByDefault('Uninstall')}</button> :
508
+ <button className="theia-button prominent action" onClick={this.install}>{nls.localizeByDefault('Install')}</button>)
509
+ }
510
+ <div className="codicon codicon-settings-gear action" tabIndex={tabIndex} onClick={this.manage}></div>
511
+ </div>;
464
512
  }
465
513
 
466
514
  }
@@ -486,7 +534,7 @@ export namespace VSXExtensionComponent {
486
534
 
487
535
  export class VSXExtensionComponent<Props extends VSXExtensionComponent.Props = VSXExtensionComponent.Props> extends AbstractVSXExtensionComponent<Props> {
488
536
  override render(): React.ReactNode {
489
- const { iconUrl, publisher, displayName, description, version, downloadCount, averageRating, tooltip, verified } = this.props.extension;
537
+ const { iconUrl, publisher, displayName, description, version, downloadCount, averageRating, tooltip, verified, disabled } = this.props.extension;
490
538
 
491
539
  return <div
492
540
  className='theia-vsx-extension noselect'
@@ -509,7 +557,9 @@ export class VSXExtensionComponent<Props extends VSXExtensionComponent.Props = V
509
557
  <div className='theia-vsx-extension-content'>
510
558
  <div className='title'>
511
559
  <div className='noWrapInfo'>
512
- <span className='name'>{displayName}</span> <span className='version'>{VSXExtension.formatVersion(version)}</span>
560
+ <span className='name'>{displayName}</span>&nbsp;
561
+ <span className='version'>{VSXExtension.formatVersion(version)}&nbsp;
562
+ </span>{disabled && <span className='disabled'>({nls.localizeByDefault('disabled')})</span>}
513
563
  </div>
514
564
  <div className='stat'>
515
565
  {!!downloadCount && <span className='download-count'><i className={codicon('cloud-download')} />{downloadCompactFormatter.format(downloadCount)}</span>}
@@ -517,6 +567,7 @@ export class VSXExtensionComponent<Props extends VSXExtensionComponent.Props = V
517
567
  </div>
518
568
  </div>
519
569
  <div className='noWrapInfo theia-vsx-extension-description'>{description}</div>
570
+
520
571
  <div className='theia-vsx-extension-action-bar'>
521
572
  <div className='theia-vsx-extension-publisher-container'>
522
573
  {verified === true ? (
@@ -114,6 +114,18 @@ export class VSXExtensionsContribution extends AbstractViewContribution<VSXExten
114
114
  execute: async (extension: VSXExtension) => this.installAnotherVersion(extension),
115
115
  });
116
116
 
117
+ commands.registerCommand(VSXExtensionsCommands.DISABLE, {
118
+ isVisible: (extension: VSXExtension) => extension.installed && !extension.disabled,
119
+ isEnabled: (extension: VSXExtension) => extension.installed && !extension.disabled,
120
+ execute: async (extension: VSXExtension) => extension.disable(),
121
+ });
122
+
123
+ commands.registerCommand(VSXExtensionsCommands.ENABLE, {
124
+ isVisible: (extension: VSXExtension) => extension.installed && extension.disabled,
125
+ isEnabled: (extension: VSXExtension) => extension.installed && extension.disabled,
126
+ execute: async (extension: VSXExtension) => extension.enable(),
127
+ });
128
+
117
129
  commands.registerCommand(VSXExtensionsCommands.COPY, {
118
130
  execute: (extension: VSXExtension) => this.copy(extension)
119
131
  });
@@ -152,6 +164,15 @@ export class VSXExtensionsContribution extends AbstractViewContribution<VSXExten
152
164
  label: nls.localizeByDefault('Copy Extension ID'),
153
165
  order: '1'
154
166
  });
167
+ menus.registerMenuAction(VSXExtensionsContextMenu.DISABLE, {
168
+ commandId: VSXExtensionsCommands.DISABLE.id,
169
+ label: nls.localizeByDefault('Disable')
170
+ });
171
+
172
+ menus.registerMenuAction(VSXExtensionsContextMenu.ENABLE, {
173
+ commandId: VSXExtensionsCommands.ENABLE.id,
174
+ label: nls.localizeByDefault('Enable')
175
+ });
155
176
  menus.registerMenuAction(VSXExtensionsContextMenu.INSTALL, {
156
177
  commandId: VSXExtensionsCommands.INSTALL_ANOTHER_VERSION.id,
157
178
  label: nls.localizeByDefault('Install Specific Version...'),
@@ -34,19 +34,24 @@ import { RequestContext, RequestService } from '@theia/core/shared/@theia/reques
34
34
  import { OVSXApiFilterProvider } from '@theia/ovsx-client';
35
35
  import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
36
36
  import { FileService } from '@theia/filesystem/lib/browser/file-service';
37
+ import { HostedPluginServer, PluginIdentifiers, PluginType } from '@theia/plugin-ext';
38
+ import { HostedPluginWatcher } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin-watcher';
37
39
 
38
40
  @injectable()
39
41
  export class VSXExtensionsModel {
40
-
41
42
  protected initialized: Promise<void>;
42
43
  /**
43
44
  * Single source for all extensions
44
45
  */
45
46
  protected readonly extensions = new Map<string, VSXExtension>();
46
47
  protected readonly onDidChangeEmitter = new Emitter<void>();
47
- protected _installed = new Set<string>();
48
+ protected disabled = new Set<PluginIdentifiers.UnversionedId>();
49
+ protected uninstalled = new Set<PluginIdentifiers.UnversionedId>();
50
+ protected deployed = new Set<PluginIdentifiers.UnversionedId>();
51
+ protected _installed = new Set<PluginIdentifiers.UnversionedId>();
48
52
  protected _recommended = new Set<string>();
49
53
  protected _searchResult = new Set<string>();
54
+ protected builtins = new Set<PluginIdentifiers.UnversionedId>();
50
55
  protected _searchError?: string;
51
56
 
52
57
  protected searchCancellationTokenSource = new CancellationTokenSource();
@@ -61,6 +66,12 @@ export class VSXExtensionsModel {
61
66
  @inject(HostedPluginSupport)
62
67
  protected readonly pluginSupport: HostedPluginSupport;
63
68
 
69
+ @inject(HostedPluginWatcher)
70
+ protected pluginWatcher: HostedPluginWatcher;
71
+
72
+ @inject(HostedPluginServer)
73
+ protected readonly pluginServer: HostedPluginServer;
74
+
64
75
  @inject(VSXExtensionFactory)
65
76
  protected readonly extensionFactory: VSXExtensionFactory;
66
77
 
@@ -128,8 +139,24 @@ export class VSXExtensionsModel {
128
139
  this.updateSearchResult();
129
140
  }
130
141
 
142
+ isBuiltIn(id: string): boolean {
143
+ return this.builtins.has(id as PluginIdentifiers.UnversionedId);
144
+ }
145
+
131
146
  isInstalled(id: string): boolean {
132
- return this._installed.has(id);
147
+ return this._installed.has(id as PluginIdentifiers.UnversionedId);
148
+ }
149
+
150
+ isUninstalled(id: string): boolean {
151
+ return this.uninstalled.has(id as PluginIdentifiers.UnversionedId);
152
+ }
153
+
154
+ isDeployed(id: string): boolean {
155
+ return this.deployed.has(id as PluginIdentifiers.UnversionedId);
156
+ }
157
+
158
+ isDisabled(id: string): boolean {
159
+ return this.disabled.has(id as PluginIdentifiers.UnversionedId);
133
160
  }
134
161
 
135
162
  getExtension(id: string): VSXExtension | undefined {
@@ -187,12 +214,15 @@ export class VSXExtensionsModel {
187
214
 
188
215
  protected async initInstalled(): Promise<void> {
189
216
  await this.pluginSupport.willStart;
190
- this.pluginSupport.onDidChangePlugins(() => this.updateInstalled());
191
217
  try {
192
218
  await this.updateInstalled();
193
219
  } catch (e) {
194
220
  console.error(e);
195
221
  }
222
+
223
+ this.pluginWatcher.onDidDeploy(() => {
224
+ this.updateInstalled();
225
+ });
196
226
  }
197
227
 
198
228
  protected async initSearchResult(): Promise<void> {
@@ -223,10 +253,10 @@ export class VSXExtensionsModel {
223
253
  return this.searchCancellationTokenSource = new CancellationTokenSource();
224
254
  }
225
255
 
226
- protected setExtension(id: string): VSXExtension {
256
+ protected setExtension(id: string, version?: string): VSXExtension {
227
257
  let extension = this.extensions.get(id);
228
258
  if (!extension) {
229
- extension = this.extensionFactory({ id });
259
+ extension = this.extensionFactory({ id, version, model: this });
230
260
  this.extensions.set(id, extension);
231
261
  }
232
262
  return extension;
@@ -328,19 +358,41 @@ export class VSXExtensionsModel {
328
358
  }
329
359
 
330
360
  protected async updateInstalled(): Promise<void> {
361
+ const [deployed, uninstalled, disabled] = await Promise.all(
362
+ [this.pluginServer.getDeployedPluginIds(), this.pluginServer.getUninstalledPluginIds(), this.pluginServer.getDisabledPluginIds()]);
363
+
364
+ this.uninstalled = new Set();
365
+ uninstalled.forEach(id => this.uninstalled.add(PluginIdentifiers.unversionedFromVersioned(id)));
366
+ this.disabled = new Set();
367
+ disabled.forEach(id => this.disabled.add(PluginIdentifiers.unversionedFromVersioned(id)));
368
+ this.deployed = new Set();
369
+ deployed.forEach(id => this.deployed.add(PluginIdentifiers.unversionedFromVersioned(id)));
370
+
331
371
  const prevInstalled = this._installed;
372
+ const installedVersioned = new Set<PluginIdentifiers.VersionedId>();
332
373
  return this.doChange(async () => {
333
- const plugins = this.pluginSupport.plugins;
334
- const currInstalled = new Set<string>();
374
+ const currInstalled = new Set<PluginIdentifiers.UnversionedId>();
335
375
  const refreshing = [];
336
- for (const plugin of plugins) {
337
- if (plugin.model.engine.type === 'vscode') {
338
- const version = plugin.model.version;
339
- const id = plugin.model.id;
340
- this._installed.delete(id);
341
- const extension = this.setExtension(id);
342
- currInstalled.add(extension.id);
343
- refreshing.push(this.refresh(id, version));
376
+ for (const versionedId of deployed) {
377
+ installedVersioned.add(versionedId);
378
+ const idAndVersion = PluginIdentifiers.idAndVersionFromVersionedId(versionedId);
379
+ if (idAndVersion) {
380
+ this._installed.delete(idAndVersion.id);
381
+ this.setExtension(idAndVersion.id, idAndVersion.version);
382
+ currInstalled.add(idAndVersion.id);
383
+ refreshing.push(this.refresh(idAndVersion.id, idAndVersion.version));
384
+ }
385
+ }
386
+ for (const versionedId of disabled) {
387
+ const idAndVersion = PluginIdentifiers.idAndVersionFromVersionedId(versionedId);
388
+ installedVersioned.add(versionedId);
389
+ if (idAndVersion && !this.isUninstalled(idAndVersion.id)) {
390
+ if (!currInstalled.has(idAndVersion.id)) {
391
+ this._installed.delete(idAndVersion.id);
392
+ this.setExtension(idAndVersion.id, idAndVersion.version);
393
+ currInstalled.add(idAndVersion.id);
394
+ refreshing.push(this.refresh(idAndVersion.id, idAndVersion.version));
395
+ }
344
396
  }
345
397
  }
346
398
  for (const id of this._installed) {
@@ -348,10 +400,33 @@ export class VSXExtensionsModel {
348
400
  if (!extension) { continue; }
349
401
  refreshing.push(this.refresh(id, extension.version));
350
402
  }
403
+ await Promise.all(refreshing);
351
404
  const installed = new Set([...prevInstalled, ...currInstalled]);
352
405
  const installedSorted = Array.from(installed).sort((a, b) => this.compareExtensions(a, b));
353
406
  this._installed = new Set(installedSorted.values());
354
- await Promise.all(refreshing);
407
+
408
+ const missingIds = new Set<PluginIdentifiers.VersionedId>();
409
+ for (const id of installedVersioned) {
410
+ const unversionedId = PluginIdentifiers.unversionedFromVersioned(id);
411
+ const plugin = this.pluginSupport.getPlugin(unversionedId);
412
+ if (plugin) {
413
+ if (plugin.type === PluginType.System) {
414
+ this.builtins.add(unversionedId);
415
+ } else {
416
+ this.builtins.delete(unversionedId);
417
+ }
418
+ } else {
419
+ missingIds.add(id);
420
+ }
421
+ }
422
+ const missing = await this.pluginServer.getDeployedPlugins([...missingIds.values()]);
423
+ for (const plugin of missing) {
424
+ if (plugin.type === PluginType.System) {
425
+ this.builtins.add(PluginIdentifiers.componentsToUnversionedId(plugin.metadata.model));
426
+ } else {
427
+ this.builtins.delete(PluginIdentifiers.componentsToUnversionedId(plugin.metadata.model));
428
+ }
429
+ }
355
430
  });
356
431
  }
357
432
 
@@ -362,7 +437,7 @@ export class VSXExtensionsModel {
362
437
 
363
438
  const updateRecommendationsForScope = (scope: PreferenceInspectionScope, root?: URI) => {
364
439
  const { recommendations, unwantedRecommendations } = this.getRecommendationsForScope(scope, root);
365
- recommendations.forEach(recommendation => allRecommendations.add(recommendation));
440
+ recommendations.forEach(recommendation => allRecommendations.add(recommendation.toLowerCase()));
366
441
  unwantedRecommendations.forEach(unwantedRecommendation => allUnwantedRecommendations.add(unwantedRecommendation));
367
442
  };
368
443
 
@@ -449,15 +524,12 @@ export class VSXExtensionsModel {
449
524
  * @param extension the extension to refresh.
450
525
  */
451
526
  protected shouldRefresh(extension?: VSXExtension): boolean {
452
- if (extension === undefined) {
453
- return true;
454
- }
455
- return !extension.builtin;
527
+ return extension === undefined || extension.plugin === undefined;
456
528
  }
457
529
 
458
530
  protected onDidFailRefresh(id: string, error: unknown): VSXExtension | undefined {
459
531
  const cached = this.getExtension(id);
460
- if (cached && cached.installed) {
532
+ if (cached && cached.deployed) {
461
533
  return cached;
462
534
  }
463
535
  console.error(`[${id}]: failed to refresh, reason:`, error);
@@ -71,7 +71,7 @@ export class VSXLanguageQuickPickService extends LanguageQuickPickService {
71
71
  });
72
72
  try {
73
73
  const extensionUri = VSCodeExtensionUri.fromId(`${extension.extension.namespace}.${extension.extension.name}`).toString();
74
- await this.pluginServer.deploy(extensionUri);
74
+ await this.pluginServer.install(extensionUri);
75
75
  } finally {
76
76
  progress.cancel();
77
77
  }