From b157bc7e5c3d0c7c163018c574c0088168b91ff8 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Thu, 27 May 2021 08:41:10 -0700 Subject: [PATCH] Support for @recommended:languages search (#124546) --- src/vs/base/common/product.ts | 1 + .../extensionRecommendationsService.ts | 9 +++++ .../browser/extensions.contribution.ts | 12 +++++-- .../extensions/browser/extensionsViews.ts | 17 ++++++++++ .../browser/languageRecommendations.ts | 34 +++++++++++++++++++ .../common/extensionRecommendations.ts | 1 + 6 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/vs/workbench/contrib/extensions/browser/languageRecommendations.ts diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index b7592ccdb64..a301b50683c 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -79,6 +79,7 @@ export interface IProductConfiguration { readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip; }; readonly extensionKeywords?: { [extension: string]: readonly string[]; }; readonly keymapExtensionTips?: readonly string[]; + readonly languageExtensionTips?: readonly string[]; readonly trustedExtensionUrlPublicKeys?: { [id: string]: string[]; }; readonly crashReporter?: { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts index b353d267df2..104d7ffc6e0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts @@ -18,6 +18,7 @@ import { ExperimentalRecommendations } from 'vs/workbench/contrib/extensions/bro import { WorkspaceRecommendations } from 'vs/workbench/contrib/extensions/browser/workspaceRecommendations'; import { FileBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/fileBasedRecommendations'; import { KeymapRecommendations } from 'vs/workbench/contrib/extensions/browser/keymapRecommendations'; +import { LanguageRecommendations } from 'vs/workbench/contrib/extensions/browser/languageRecommendations'; import { ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; import { ConfigBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/configBasedRecommendations'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; @@ -40,6 +41,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte private readonly exeBasedRecommendations: ExeBasedRecommendations; private readonly dynamicWorkspaceRecommendations: DynamicWorkspaceRecommendations; private readonly keymapRecommendations: KeymapRecommendations; + private readonly languageRecommendations: LanguageRecommendations; public readonly activationPromise: Promise; private sessionSeed: number; @@ -66,6 +68,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte this.exeBasedRecommendations = instantiationService.createInstance(ExeBasedRecommendations); this.dynamicWorkspaceRecommendations = instantiationService.createInstance(DynamicWorkspaceRecommendations); this.keymapRecommendations = instantiationService.createInstance(KeymapRecommendations); + this.languageRecommendations = instantiationService.createInstance(LanguageRecommendations); if (!this.isEnabled()) { this.sessionSeed = 0; @@ -90,6 +93,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte this.fileBasedRecommendations.activate(), this.experimentalRecommendations.activate(), this.keymapRecommendations.activate(), + this.languageRecommendations.activate(), ]); this._register(Event.any(this.workspaceRecommendations.onDidChangeRecommendations, this.configBasedRecommendations.onDidChangeRecommendations, this.extensionRecommendationsManagementService.onDidChangeIgnoredRecommendations)(() => this._onDidChangeRecommendations.fire())); @@ -127,6 +131,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte ...this.fileBasedRecommendations.recommendations, ...this.workspaceRecommendations.recommendations, ...this.keymapRecommendations.recommendations, + ...this.languageRecommendations.recommendations, ]; for (const { extensionId, reason } of allRecommendations) { @@ -185,6 +190,10 @@ export class ExtensionRecommendationsService extends Disposable implements IExte return this.toExtensionRecommendations(this.keymapRecommendations.recommendations); } + getLanguageRecommendations(): string[] { + return this.toExtensionRecommendations(this.languageRecommendations.recommendations); + } + async getWorkspaceRecommendations(): Promise { if (!this.isEnabled()) { return []; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 25794f976cf..96605e1672b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -23,7 +23,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor, optional } from 'vs/platform/instantiation/common/instantiation'; import { KeymapExtensions } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor'; @@ -73,6 +73,7 @@ import { Promises } from 'vs/base/common/async'; import { EditorExtensions } from 'vs/workbench/common/editor'; import { WORKSPACE_TRUST_EXTENSION_SUPPORT } from 'vs/workbench/services/workspaces/common/workspaceTrust'; import { ExtensionsCompletionItemsProvider } from 'vs/workbench/contrib/extensions/browser/extensionsCompletionItemsProvider'; +import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -378,6 +379,8 @@ interface IExtensionActionOptions extends IAction2Options { class ExtensionsContributions extends Disposable implements IWorkbenchContribution { + private tasExperimentService?: ITASExperimentService; + constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @@ -388,6 +391,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, + @optional(ITASExperimentService) tasExperimentService: ITASExperimentService, ) { super(); const hasGalleryContext = CONTEXT_HAS_GALLERY.bindTo(contextKeyService); @@ -410,6 +414,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi hasWebServerContext.set(true); } + this.tasExperimentService = tasExperimentService; this.registerGlobalActions(); this.registerContextMenuActions(); this.registerQuickAccessProvider(); @@ -501,7 +506,10 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: MenuId.CommandPalette, when: CONTEXT_HAS_GALLERY }, - run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@category:"programming languages" @sort:installs ')) + run: async () => { + const recommended = await this.tasExperimentService?.getTreatment('recommendedLanguages'); + runAction(this.instantiationService.createInstance(SearchExtensionsAction, recommended ? '@recommended:languages ' : '@category:"programming languages" @sort:installs ')); + } }); this.registerExtensionAction({ diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 79110057fb3..d8fb4ab275a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -739,6 +739,11 @@ export class ExtensionsListView extends ViewPane { return this.getKeymapRecommendationsModel(query, options, token); } + // Language recommendations + if (ExtensionsListView.isLanguageRecommendedExtensionsQuery(query.value)) { + return this.getLanguageRecommendationsModel(query, options, token); + } + // Exe recommendations if (ExtensionsListView.isExeRecommendedExtensionsQuery(query.value)) { return this.getExeRecommendationsModel(query, options, token); @@ -803,6 +808,14 @@ export class ExtensionsListView extends ViewPane { return new PagedModel(installableRecommendations); } + private async getLanguageRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { + const value = query.value.replace(/@recommended:languages/g, '').trim().toLowerCase(); + const recommendations = this.extensionRecommendationsService.getLanguageRecommendations(); + const installableRecommendations = (await this.getInstallableRecommendations(recommendations, { ...options, source: 'recommendations-languages' }, token)) + .filter(extension => extension.identifier.id.toLowerCase().indexOf(value) > -1); + return new PagedModel(installableRecommendations); + } + private async getExeRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { const exe = query.value.replace(/@exe:/g, '').trim().toLowerCase(); const { important, others } = await this.extensionRecommendationsService.getExeBasedRecommendations(exe.startsWith('"') ? exe.substring(1, exe.length - 1) : exe); @@ -1035,6 +1048,10 @@ export class ExtensionsListView extends ViewPane { return /@recommended:keymaps/i.test(query); } + static isLanguageRecommendedExtensionsQuery(query: string): boolean { + return /@recommended:languages/i.test(query); + } + override focus(): void { super.focus(); if (!this.list) { diff --git a/src/vs/workbench/contrib/extensions/browser/languageRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/languageRecommendations.ts new file mode 100644 index 00000000000..9258305b84a --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/languageRecommendations.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; + +export class LanguageRecommendations extends ExtensionRecommendations { + + private _recommendations: ExtensionRecommendation[] = []; + get recommendations(): ReadonlyArray { return this._recommendations; } + + constructor( + @IProductService private readonly productService: IProductService, + ) { + super(); + } + + protected async doActivate(): Promise { + if (this.productService.languageExtensionTips) { + this._recommendations = this.productService.languageExtensionTips.map(extensionId => ({ + extensionId: extensionId.toLowerCase(), + reason: { + reasonId: ExtensionRecommendationReason.Application, + reasonText: '' + } + })); + } + } + +} + diff --git a/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts b/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts index 0d76094d021..3d993c896ca 100644 --- a/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts +++ b/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts @@ -44,6 +44,7 @@ export interface IExtensionRecommendationsService { getConfigBasedRecommendations(): Promise<{ important: string[], others: string[] }>; getWorkspaceRecommendations(): Promise; getKeymapRecommendations(): string[]; + getLanguageRecommendations(): string[]; } export type IgnoredRecommendationChangeNotification = {