mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-27 02:27:53 +01:00
Improve exe based recommendations
This commit is contained in:
@@ -239,6 +239,7 @@ export type IExecutableBasedExtensionTip = {
|
||||
readonly extensionId: string,
|
||||
readonly extensionName: string,
|
||||
readonly isExtensionPack: boolean,
|
||||
readonly exeName: string,
|
||||
readonly exeFriendlyName: string,
|
||||
readonly windowsPath?: string,
|
||||
};
|
||||
|
||||
@@ -105,6 +105,7 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
|
||||
extensionId,
|
||||
extensionName,
|
||||
isExtensionPack,
|
||||
exeName,
|
||||
exeFriendlyName: extensionTip.exeFriendlyName,
|
||||
windowsPath: extensionTip.windowsPath,
|
||||
});
|
||||
|
||||
@@ -94,7 +94,7 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations {
|
||||
const tip = this.importantTips.filter(tip => tip.extensionId === extension)[0];
|
||||
const message = tip.isExtensionPack ? localize('extensionPackRecommended', "The '{0}' extension pack is recommended for this workspace.", tip.extensionName)
|
||||
: localize('extensionRecommended', "The '{0}' extension is recommended for this workspace.", tip.extensionName);
|
||||
this.promptImportantExtensionsInstallNotification([extension], message);
|
||||
this.promptImportantExtensionsInstallNotification([extension], message, extension, localize('more information', "More Information"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@ type ExeExtensionRecommendationsClassification = {
|
||||
export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
|
||||
|
||||
private readonly _otherRecommendations: ExtensionRecommendation[] = [];
|
||||
get otherRecommendations(): ReadonlyArray<ExtensionRecommendation> { return this._otherRecommendations; }
|
||||
private _otherTips: IExecutableBasedExtensionTip[] = [];
|
||||
private _importantTips: IExecutableBasedExtensionTip[] = [];
|
||||
|
||||
private readonly _importantRecommendations: ExtensionRecommendation[] = [];
|
||||
get importantRecommendations(): ReadonlyArray<ExtensionRecommendation> { return this._importantRecommendations; }
|
||||
get otherRecommendations(): ReadonlyArray<ExtensionRecommendation> { return this._otherTips.map(tip => this.toExtensionRecommendation(tip)); }
|
||||
get importantRecommendations(): ReadonlyArray<ExtensionRecommendation> { return this._importantTips.map(tip => this.toExtensionRecommendation(tip)); }
|
||||
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return [...this.importantRecommendations, ...this.otherRecommendations]; }
|
||||
|
||||
@@ -58,9 +58,20 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
timeout(3000).then(() => this.fetchAndPromptImportantExeBasedRecommendations());
|
||||
}
|
||||
|
||||
getRecommendations(exe: string): { important: ExtensionRecommendation[], others: ExtensionRecommendation[] } {
|
||||
const important = this._importantTips
|
||||
.filter(tip => tip.exeName.toLowerCase() === exe.toLowerCase())
|
||||
.map(tip => this.toExtensionRecommendation(tip));
|
||||
|
||||
const others = this._otherTips
|
||||
.filter(tip => tip.exeName.toLowerCase() === exe.toLowerCase())
|
||||
.map(tip => this.toExtensionRecommendation(tip));
|
||||
|
||||
return { important, others };
|
||||
}
|
||||
|
||||
protected async doActivate(): Promise<void> {
|
||||
const otherExectuableBasedTips = await this.extensionTipsService.getOtherExecutableBasedTips();
|
||||
otherExectuableBasedTips.forEach(tip => this._otherRecommendations.push(this.toExtensionRecommendation(tip)));
|
||||
this._otherTips = await this.extensionTipsService.getOtherExecutableBasedTips();
|
||||
await this.fetchImportantExeBasedRecommendations();
|
||||
}
|
||||
|
||||
@@ -74,11 +85,8 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
|
||||
private async doFetchImportantExeBasedRecommendations(): Promise<IStringDictionary<IExecutableBasedExtensionTip>> {
|
||||
const importantExeBasedRecommendations: IStringDictionary<IExecutableBasedExtensionTip> = {};
|
||||
const importantExectuableBasedTips = await this.extensionTipsService.getImportantExecutableBasedTips();
|
||||
importantExectuableBasedTips.forEach(tip => {
|
||||
this._importantRecommendations.push(this.toExtensionRecommendation(tip));
|
||||
importantExeBasedRecommendations[tip.extensionId.toLowerCase()] = tip;
|
||||
});
|
||||
this._importantTips = await this.extensionTipsService.getImportantExecutableBasedTips();
|
||||
this._importantTips.forEach(tip => importantExeBasedRecommendations[tip.extensionId.toLowerCase()] = tip);
|
||||
return importantExeBasedRecommendations;
|
||||
}
|
||||
|
||||
@@ -127,22 +135,8 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
await this.tasExperimentService.getTreatment<boolean>('wslpopupaa');
|
||||
}
|
||||
|
||||
if (tips.length === 1) {
|
||||
const tip = tips[0];
|
||||
const message = tip.isExtensionPack ? localize('extensionPackRecommended', "The '{0}' extension pack is recommended as you have {1} installed on your system.", tip.extensionName, tip.exeFriendlyName || basename(tip.windowsPath!))
|
||||
: localize('exeRecommended', "The '{0}' extension is recommended as you have {1} installed on your system.", tip.extensionName, tip.exeFriendlyName || basename(tip.windowsPath!));
|
||||
this.promptImportantExtensionsInstallNotification(extensionIds, message);
|
||||
}
|
||||
|
||||
else if (tips.length === 2) {
|
||||
const message = localize('two extensions recommended', "The '{0}' and '{1}' extensions are recommended as you have {2} installed on your system.", tips[0].extensionName, tips[1].extensionName, tips[0].exeFriendlyName || basename(tips[0].windowsPath!));
|
||||
this.promptImportantExtensionsInstallNotification(extensionIds, message);
|
||||
}
|
||||
|
||||
else if (tips.length > 2) {
|
||||
const message = localize('more than two extensions recommended', "The '{0}', '{1}' and other extensions are recommended as you have {2} installed on your system.", tips[0].extensionName, tips[1].extensionName, tips[0].exeFriendlyName || basename(tips[0].windowsPath!));
|
||||
this.promptImportantExtensionsInstallNotification(extensionIds, message);
|
||||
}
|
||||
const message = localize('exeRecommended', "You have {0} installed on your system. Do you want to install recommendations for it?", tips[0].exeFriendlyName);
|
||||
this.promptImportantExtensionsInstallNotification(extensionIds, message, `@exe:"${tips[0].exeName}"`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { localize } from 'vs/nls';
|
||||
import { InstallRecommendedExtensionAction, ShowRecommendedExtensionAction, ShowRecommendedExtensionsAction, InstallRecommendedExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { InstallRecommendedExtensionsAction, SearchExtensionsAction, OpenExtensionEditorAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { ExtensionRecommendationSource, IExtensionRecommendationReson } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionsConfiguration, ConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -57,38 +57,33 @@ export abstract class ExtensionRecommendations extends Disposable {
|
||||
return this._activationPromise;
|
||||
}
|
||||
|
||||
private runAction(action: IAction) {
|
||||
private async runAction(action: IAction): Promise<void> {
|
||||
try {
|
||||
action.run();
|
||||
await action.run();
|
||||
} finally {
|
||||
action.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected promptImportantExtensionsInstallNotification(extensionIds: string[], message: string): void {
|
||||
protected promptImportantExtensionsInstallNotification(extensionIds: string[], message: string, searchValue: string, showRecommendationsLabel?: string): void {
|
||||
this.notificationService.prompt(Severity.Info, message,
|
||||
[{
|
||||
label: extensionIds.length === 1 ? localize('install', 'Install') : localize('installAll', "Install All"),
|
||||
label: localize('install', 'Install'),
|
||||
run: async () => {
|
||||
for (const extensionId of extensionIds) {
|
||||
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'install', extensionId });
|
||||
}
|
||||
if (extensionIds.length === 1) {
|
||||
this.runAction(this.instantiationService.createInstance(InstallRecommendedExtensionAction, extensionIds[0]));
|
||||
} else {
|
||||
this.runAction(this.instantiationService.createInstance(InstallRecommendedExtensionsAction, InstallRecommendedExtensionsAction.ID, InstallRecommendedExtensionsAction.LABEL, extensionIds, 'install-recommendations'));
|
||||
}
|
||||
this.runAction(this.instantiationService.createInstance(InstallRecommendedExtensionsAction, InstallRecommendedExtensionsAction.ID, InstallRecommendedExtensionsAction.LABEL, extensionIds, searchValue, 'install-recommendations'));
|
||||
}
|
||||
}, {
|
||||
label: extensionIds.length === 1 ? localize('moreInformation', "More Information") : localize('showRecommendations', "Show Recommendations"),
|
||||
run: () => {
|
||||
label: showRecommendationsLabel || localize('show recommendations', "Show Recommendations"),
|
||||
run: async () => {
|
||||
for (const extensionId of extensionIds) {
|
||||
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId });
|
||||
}
|
||||
this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue));
|
||||
if (extensionIds.length === 1) {
|
||||
this.runAction(this.instantiationService.createInstance(ShowRecommendedExtensionAction, extensionIds[0]));
|
||||
} else {
|
||||
this.runAction(this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL));
|
||||
this.runAction(this.instantiationService.createInstance(OpenExtensionEditorAction, extensionIds[0]));
|
||||
}
|
||||
}
|
||||
}, {
|
||||
|
||||
@@ -191,6 +191,13 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
return this.toExtensionRecommendations(this.workspaceRecommendations.recommendations);
|
||||
}
|
||||
|
||||
async getExeBasedRecommendations(exe?: string): Promise<{ important: IExtensionRecommendation[], others: IExtensionRecommendation[] }> {
|
||||
await this.exeBasedRecommendations.activate();
|
||||
const { important, others } = exe ? this.exeBasedRecommendations.getRecommendations(exe)
|
||||
: { important: this.exeBasedRecommendations.importantRecommendations, others: this.exeBasedRecommendations.otherRecommendations };
|
||||
return { important: this.toExtensionRecommendations(important), others: this.toExtensionRecommendations(others) };
|
||||
}
|
||||
|
||||
getFileBasedRecommendations(): IExtensionRecommendation[] {
|
||||
return this.toExtensionRecommendations(this.fileBasedRecommendations.recommendations);
|
||||
}
|
||||
|
||||
@@ -1842,6 +1842,7 @@ export class InstallRecommendedExtensionsAction extends Action {
|
||||
id: string,
|
||||
label: string,
|
||||
recommendations: string[],
|
||||
private readonly searchValue: string,
|
||||
private readonly source: string,
|
||||
@IViewletService private readonly viewletService: IViewletService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@@ -1854,22 +1855,17 @@ export class InstallRecommendedExtensionsAction extends Action {
|
||||
this.recommendations = recommendations;
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
return this.viewletService.openViewlet(VIEWLET_ID, true)
|
||||
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
|
||||
.then(viewlet => {
|
||||
viewlet.search('@recommended ');
|
||||
viewlet.focus();
|
||||
const names = this.recommendations;
|
||||
return this.extensionWorkbenchService.queryGallery({ names, source: this.source }, CancellationToken.None).then(pager => {
|
||||
let installPromises: Promise<any>[] = [];
|
||||
let model = new PagedModel(pager);
|
||||
for (let i = 0; i < pager.total; i++) {
|
||||
installPromises.push(model.resolve(i, CancellationToken.None).then(e => this.installExtension(e)));
|
||||
}
|
||||
return Promise.all(installPromises);
|
||||
});
|
||||
});
|
||||
async run(): Promise<any> {
|
||||
await new SearchExtensionsAction(this.searchValue, this.viewletService).run();
|
||||
const names = this.recommendations;
|
||||
const pager = await this.extensionWorkbenchService.queryGallery({ names, source: this.source }, CancellationToken.None);
|
||||
const installPromises: Promise<any>[] = [];
|
||||
const model = new PagedModel(pager);
|
||||
for (let i = 0; i < pager.total; i++) {
|
||||
installPromises.push(model.resolve(i, CancellationToken.None)
|
||||
.then(e => this.installExtension(e)));
|
||||
}
|
||||
return Promise.all(installPromises);
|
||||
}
|
||||
|
||||
private async installExtension(extension: IExtension): Promise<void> {
|
||||
@@ -1885,6 +1881,7 @@ export class InstallRecommendedExtensionsAction extends Action {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.extensionWorkbenchService.open(extension, { pinned: true });
|
||||
await this.extensionWorkbenchService.install(extension);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -1904,7 +1901,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends InstallRecommen
|
||||
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IProductService productService: IProductService,
|
||||
) {
|
||||
super('workbench.extensions.action.installWorkspaceRecommendedExtensions', localize('installWorkspaceRecommendedExtensions', "Install Workspace Recommended Extensions"), recommendations, 'install-all-workspace-recommendations',
|
||||
super('workbench.extensions.action.installWorkspaceRecommendedExtensions', localize('installWorkspaceRecommendedExtensions', "Install Workspace Recommended Extensions"), recommendations, '@recommended ', 'install-all-workspace-recommendations',
|
||||
viewletService, instantiationService, extensionWorkbenchService, configurationService, extensionManagementServerService, productService);
|
||||
}
|
||||
}
|
||||
@@ -1943,6 +1940,24 @@ export class ShowRecommendedExtensionAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenExtensionEditorAction extends Action {
|
||||
|
||||
constructor(
|
||||
private readonly extensionId: string,
|
||||
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
|
||||
) {
|
||||
super('extensions.openExtension', localize('open extension', "Open Extension"), undefined, true);
|
||||
}
|
||||
|
||||
async run(): Promise<any> {
|
||||
const pager = await this.extensionWorkbenchService.queryGallery({ names: [this.extensionId], source: 'install-recommendation', pageSize: 1 }, CancellationToken.None);
|
||||
if (pager && pager.firstPage && pager.firstPage.length) {
|
||||
const extension = pager.firstPage[0];
|
||||
return this.extensionWorkbenchService.open(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class InstallRecommendedExtensionAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.extensions.action.installRecommendedExtension';
|
||||
@@ -2087,12 +2102,23 @@ export class SearchCategoryAction extends Action {
|
||||
}
|
||||
|
||||
run(): Promise<void> {
|
||||
return this.viewletService.openViewlet(VIEWLET_ID, true)
|
||||
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
|
||||
.then(viewlet => {
|
||||
viewlet.search(`@category:"${this.category.toLowerCase()}"`);
|
||||
viewlet.focus();
|
||||
});
|
||||
return new SearchExtensionsAction(`@category:"${this.category.toLowerCase()}"`, this.viewletService).run();
|
||||
}
|
||||
}
|
||||
|
||||
export class SearchExtensionsAction extends Action {
|
||||
|
||||
constructor(
|
||||
private readonly searchValue: string,
|
||||
@IViewletService private readonly viewletService: IViewletService
|
||||
) {
|
||||
super('extensions.searchExtensions', localize('search recommendations', "Search Extensions"), undefined, true);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const viewPaneContainer = (await this.viewletService.openViewlet(VIEWLET_ID, true))?.getViewPaneContainer() as IExtensionsViewPaneContainer;
|
||||
viewPaneContainer.search(this.searchValue);
|
||||
viewPaneContainer.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -460,6 +460,8 @@ export class ExtensionsListView extends ViewPane {
|
||||
return this.getWorkspaceRecommendationsModel(query, options, token);
|
||||
} else if (ExtensionsListView.isKeymapsRecommendedExtensionsQuery(query.value)) {
|
||||
return this.getKeymapRecommendationsModel(query, options, token);
|
||||
} else if (ExtensionsListView.isExeRecommendedExtensionsQuery(query.value)) {
|
||||
return this.getExeRecommendationsModel(query, options, token);
|
||||
} else if (/@recommended:all/i.test(query.value) || ExtensionsListView.isSearchRecommendedExtensionsQuery(query.value)) {
|
||||
return this.getAllRecommendationsModel(query, options, token);
|
||||
} else if (ExtensionsListView.isRecommendedExtensionsQuery(query.value)) {
|
||||
@@ -732,6 +734,19 @@ export class ExtensionsListView extends ViewPane {
|
||||
.then(result => this.getPagedModel(result));
|
||||
}
|
||||
|
||||
private async getExeRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
|
||||
const exe = query.value.replace(/@exe:/g, '').trim().toLowerCase();
|
||||
const { important } = await this.tipsService.getExeBasedRecommendations(exe.startsWith('"') ? exe.substring(1, exe.length - 1) : exe);
|
||||
const names: string[] = important.map(({ extensionId }) => extensionId);
|
||||
|
||||
if (!names.length) {
|
||||
return Promise.resolve(new PagedModel([]));
|
||||
}
|
||||
options.source = 'recommendations-exe';
|
||||
return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token)
|
||||
.then(result => this.getPagedModel(result));
|
||||
}
|
||||
|
||||
// Sorts the firstPage of the pager in the same order as given array of extension ids
|
||||
private sortFirstPage(pager: IPager<IExtension>, ids: string[]) {
|
||||
ids = ids.map(x => x.toLowerCase());
|
||||
@@ -864,6 +879,10 @@ export class ExtensionsListView extends ViewPane {
|
||||
return /@recommended:workspace/i.test(query);
|
||||
}
|
||||
|
||||
static isExeRecommendedExtensionsQuery(query: string): boolean {
|
||||
return /@exe:.+/i.test(query);
|
||||
}
|
||||
|
||||
static isKeymapsRecommendedExtensionsQuery(query: string): boolean {
|
||||
return /@recommended:keymaps/i.test(query);
|
||||
}
|
||||
|
||||
@@ -226,13 +226,8 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
const extensionName = entry.name;
|
||||
let message = localize('reallyRecommended2', "The '{0}' extension is recommended for this file type.", extensionName);
|
||||
if (entry.isExtensionPack) {
|
||||
message = localize('reallyRecommendedExtensionPack', "The '{0}' extension pack is recommended for this file type.", extensionName);
|
||||
}
|
||||
|
||||
this.promptImportantExtensionsInstallNotification([extensionId], message);
|
||||
this.promptImportantExtensionsInstallNotification([extensionId], localize('reallyRecommended', "Do you want to install recommendations for this file?"), extensionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ export interface IExtensionRecommendationsService {
|
||||
|
||||
getAllRecommendationsWithReason(): IStringDictionary<IExtensionRecommendationReson>;
|
||||
getFileBasedRecommendations(): IExtensionRecommendation[];
|
||||
getExeBasedRecommendations(exe?: string): Promise<{ important: IExtensionRecommendation[], others: IExtensionRecommendation[] }>;
|
||||
getImportantRecommendations(): Promise<IExtensionRecommendation[]>;
|
||||
getConfigBasedRecommendations(): Promise<IExtensionRecommendation[]>;
|
||||
getOtherRecommendations(): Promise<IExtensionRecommendation[]>;
|
||||
|
||||
Reference in New Issue
Block a user