mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-29 16:20:29 -04:00
Merge from vscode cfbd1999769f4f08dce29629fb92fdc0fac53829
This commit is contained in:
@@ -21,8 +21,13 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations {
|
||||
private importantTips: IConfigBasedExtensionTip[] = [];
|
||||
private otherTips: IConfigBasedExtensionTip[] = [];
|
||||
|
||||
private _recommendations: ExtensionRecommendation[] = [];
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
||||
private _otherRecommendations: ExtensionRecommendation[] = [];
|
||||
get otherRecommendations(): ReadonlyArray<ExtensionRecommendation> { return this._otherRecommendations; }
|
||||
|
||||
private _importantRecommendations: ExtensionRecommendation[] = [];
|
||||
get importantRecommendations(): ReadonlyArray<ExtensionRecommendation> { return this._importantRecommendations; }
|
||||
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return [...this.importantRecommendations, ...this.otherRecommendations]; }
|
||||
|
||||
constructor(
|
||||
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
|
||||
@@ -61,7 +66,8 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations {
|
||||
}
|
||||
this.importantTips = [...importantTips.values()];
|
||||
this.otherTips = [...otherTips.values()].filter(tip => !importantTips.has(tip.extensionId));
|
||||
this._recommendations = [...this.importantTips, ...this.otherTips].map(tip => this.toExtensionRecommendation(tip));
|
||||
this._otherRecommendations = this.otherTips.map(tip => this.toExtensionRecommendation(tip));
|
||||
this._importantRecommendations = this.importantTips.map(tip => this.toExtensionRecommendation(tip));
|
||||
}
|
||||
|
||||
private async promptWorkspaceRecommendations(): Promise<void> {
|
||||
@@ -88,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.promptImportantExtensionInstallNotification(extension, message);
|
||||
this.promptImportantExtensionsInstallNotification([extension], message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,14 @@ import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
|
||||
|
||||
type ExeExtensionRecommendationsClassification = {
|
||||
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
@@ -24,13 +25,22 @@ type ExeExtensionRecommendationsClassification = {
|
||||
|
||||
export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
|
||||
readonly _recommendations: ExtensionRecommendation[] = [];
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
||||
|
||||
private readonly _otherRecommendations: ExtensionRecommendation[] = [];
|
||||
get otherRecommendations(): ReadonlyArray<ExtensionRecommendation> { return this._otherRecommendations; }
|
||||
|
||||
private readonly _importantRecommendations: ExtensionRecommendation[] = [];
|
||||
get importantRecommendations(): ReadonlyArray<ExtensionRecommendation> { return this._importantRecommendations; }
|
||||
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return [...this.importantRecommendations, ...this.otherRecommendations]; }
|
||||
|
||||
private readonly tasExperimentService: ITASExperimentService | undefined;
|
||||
|
||||
constructor(
|
||||
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
|
||||
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@optional(ITASExperimentService) tasExperimentService: ITASExperimentService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@@ -39,6 +49,7 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
) {
|
||||
super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService);
|
||||
this.tasExperimentService = tasExperimentService;
|
||||
|
||||
/*
|
||||
3s has come out to be the good number to fetch and prompt important exe based recommendations
|
||||
@@ -49,16 +60,30 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
|
||||
protected async doActivate(): Promise<void> {
|
||||
const otherExectuableBasedTips = await this.extensionTipsService.getOtherExecutableBasedTips();
|
||||
otherExectuableBasedTips.forEach(tip => this._recommendations.push(this.toExtensionRecommendation(tip)));
|
||||
otherExectuableBasedTips.forEach(tip => this._otherRecommendations.push(this.toExtensionRecommendation(tip)));
|
||||
await this.fetchImportantExeBasedRecommendations();
|
||||
}
|
||||
|
||||
private async fetchAndPromptImportantExeBasedRecommendations(): Promise<void> {
|
||||
private _importantExeBasedRecommendations: Promise<IStringDictionary<IExecutableBasedExtensionTip>> | undefined;
|
||||
private async fetchImportantExeBasedRecommendations(): Promise<IStringDictionary<IExecutableBasedExtensionTip>> {
|
||||
if (!this._importantExeBasedRecommendations) {
|
||||
this._importantExeBasedRecommendations = this.doFetchImportantExeBasedRecommendations();
|
||||
}
|
||||
return this._importantExeBasedRecommendations;
|
||||
}
|
||||
|
||||
private async doFetchImportantExeBasedRecommendations(): Promise<IStringDictionary<IExecutableBasedExtensionTip>> {
|
||||
const importantExeBasedRecommendations: IStringDictionary<IExecutableBasedExtensionTip> = {};
|
||||
const importantExectuableBasedTips = await this.extensionTipsService.getImportantExecutableBasedTips();
|
||||
importantExectuableBasedTips.forEach(tip => {
|
||||
this._recommendations.push(this.toExtensionRecommendation(tip));
|
||||
this._importantRecommendations.push(this.toExtensionRecommendation(tip));
|
||||
importantExeBasedRecommendations[tip.extensionId.toLowerCase()] = tip;
|
||||
});
|
||||
return importantExeBasedRecommendations;
|
||||
}
|
||||
|
||||
private async fetchAndPromptImportantExeBasedRecommendations(): Promise<void> {
|
||||
const importantExeBasedRecommendations = await this.fetchImportantExeBasedRecommendations();
|
||||
|
||||
const local = await this.extensionManagementService.getInstalled();
|
||||
const { installed, uninstalled } = this.groupByInstalled(Object.keys(importantExeBasedRecommendations), local);
|
||||
@@ -76,7 +101,7 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
this.promptImportantExeBasedRecommendations(uninstalled, importantExeBasedRecommendations);
|
||||
}
|
||||
|
||||
private promptImportantExeBasedRecommendations(recommendations: string[], importantExeBasedRecommendations: IStringDictionary<IExecutableBasedExtensionTip>): void {
|
||||
private async promptImportantExeBasedRecommendations(recommendations: string[], importantExeBasedRecommendations: IStringDictionary<IExecutableBasedExtensionTip>): Promise<void> {
|
||||
if (this.hasToIgnoreRecommendationNotifications()) {
|
||||
return;
|
||||
}
|
||||
@@ -85,11 +110,39 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
return;
|
||||
}
|
||||
|
||||
const recommendationsByExe = new Map<string, IExecutableBasedExtensionTip[]>();
|
||||
for (const extensionId of recommendations) {
|
||||
const tip = importantExeBasedRecommendations[extensionId];
|
||||
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.promptImportantExtensionInstallNotification(extensionId, message);
|
||||
let tips = recommendationsByExe.get(tip.exeFriendlyName);
|
||||
if (!tips) {
|
||||
tips = [];
|
||||
recommendationsByExe.set(tip.exeFriendlyName, tips);
|
||||
}
|
||||
tips.push(tip);
|
||||
}
|
||||
|
||||
for (const [, tips] of recommendationsByExe) {
|
||||
const extensionIds = tips.map(({ extensionId }) => extensionId.toLowerCase());
|
||||
if (this.tasExperimentService && extensionIds.indexOf('ms-vscode-remote.remote-wsl') !== -1) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +165,7 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
source: 'executable',
|
||||
reason: {
|
||||
reasonId: ExtensionRecommendationReason.Executable,
|
||||
reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", tip.extensionName)
|
||||
reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", tip.exeFriendlyName || basename(tip.windowsPath!))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { InstallRecommendedExtensionAction, ShowRecommendedExtensionAction, ShowRecommendedExtensionsAction, InstallRecommendedExtensionsAction } 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';
|
||||
@@ -65,26 +65,40 @@ export abstract class ExtensionRecommendations extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
protected promptImportantExtensionInstallNotification(extensionId: string, message: string): void {
|
||||
protected promptImportantExtensionsInstallNotification(extensionIds: string[], message: string): void {
|
||||
this.notificationService.prompt(Severity.Info, message,
|
||||
[{
|
||||
label: localize('install', 'Install'),
|
||||
run: () => {
|
||||
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'install', extensionId });
|
||||
this.runAction(this.instantiationService.createInstance(InstallRecommendedExtensionAction, extensionId));
|
||||
label: extensionIds.length === 1 ? localize('install', 'Install') : localize('installAll', "Install All"),
|
||||
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'));
|
||||
}
|
||||
}
|
||||
}, {
|
||||
label: localize('moreInformation', "More Information"),
|
||||
label: extensionIds.length === 1 ? localize('moreInformation', "More Information") : localize('showRecommendations', "Show Recommendations"),
|
||||
run: () => {
|
||||
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId });
|
||||
this.runAction(this.instantiationService.createInstance(ShowRecommendedExtensionAction, extensionId));
|
||||
for (const extensionId of extensionIds) {
|
||||
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId });
|
||||
}
|
||||
if (extensionIds.length === 1) {
|
||||
this.runAction(this.instantiationService.createInstance(ShowRecommendedExtensionAction, extensionIds[0]));
|
||||
} else {
|
||||
this.runAction(this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL));
|
||||
}
|
||||
}
|
||||
}, {
|
||||
label: choiceNever,
|
||||
isSecondary: true,
|
||||
run: () => {
|
||||
this.addToImportantRecommendationsIgnore(extensionId);
|
||||
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId });
|
||||
for (const extensionId of extensionIds) {
|
||||
this.addToImportantRecommendationsIgnore(extensionId);
|
||||
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId });
|
||||
}
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"),
|
||||
@@ -101,7 +115,9 @@ export abstract class ExtensionRecommendations extends Disposable {
|
||||
{
|
||||
sticky: true,
|
||||
onCancel: () => {
|
||||
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId });
|
||||
for (const extensionId of extensionIds) {
|
||||
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId });
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -152,8 +152,8 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
await this.activateProactiveRecommendations();
|
||||
|
||||
const recommendations = [
|
||||
...this.configBasedRecommendations.recommendations,
|
||||
...this.exeBasedRecommendations.recommendations,
|
||||
...this.configBasedRecommendations.otherRecommendations,
|
||||
...this.exeBasedRecommendations.otherRecommendations,
|
||||
...this.dynamicWorkspaceRecommendations.recommendations,
|
||||
...this.experimentalRecommendations.recommendations,
|
||||
...this.staticRecommendations.recommendations
|
||||
@@ -170,6 +170,26 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
});
|
||||
}
|
||||
|
||||
async getImportantRecommendations(): Promise<IExtensionRecommendation[]> {
|
||||
await this.activateProactiveRecommendations();
|
||||
|
||||
const recommendations = [
|
||||
...this.fileBasedRecommendations.importantRecommendations,
|
||||
...this.configBasedRecommendations.importantRecommendations,
|
||||
...this.exeBasedRecommendations.importantRecommendations,
|
||||
];
|
||||
|
||||
const extensionIds = distinct(recommendations.map(e => e.extensionId))
|
||||
.filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId));
|
||||
|
||||
shuffle(extensionIds, this.sessionSeed);
|
||||
|
||||
return extensionIds.map(extensionId => {
|
||||
const sources: ExtensionRecommendationSource[] = distinct(recommendations.filter(r => r.extensionId === extensionId).map(r => r.source));
|
||||
return (<IExtensionRecommendation>{ extensionId, sources });
|
||||
});
|
||||
}
|
||||
|
||||
getKeymapRecommendations(): IExtensionRecommendation[] {
|
||||
return this.toExtensionRecommendations(this.keymapRecommendations.recommendations);
|
||||
}
|
||||
|
||||
@@ -1111,7 +1111,6 @@ export class CheckForUpdatesAction extends Action {
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@IViewletService private readonly viewletService: IViewletService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@INotificationService private readonly notificationService: INotificationService
|
||||
) {
|
||||
super(id, label, '', true);
|
||||
@@ -1120,7 +1119,7 @@ export class CheckForUpdatesAction extends Action {
|
||||
private checkUpdatesAndNotify(): void {
|
||||
const outdated = this.extensionsWorkbenchService.outdated;
|
||||
if (!outdated.length) {
|
||||
this.dialogService.show(Severity.Info, localize('noUpdatesAvailable', "All extensions are up to date."), [localize('ok', "OK")]);
|
||||
this.notificationService.info(localize('noUpdatesAvailable', "All extensions are up to date."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1851,19 +1850,20 @@ export class ShowRecommendedExtensionsAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class InstallWorkspaceRecommendedExtensionsAction extends Action {
|
||||
export class InstallRecommendedExtensionsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.extensions.action.installWorkspaceRecommendedExtensions';
|
||||
static readonly LABEL = localize('installWorkspaceRecommendedExtensions', "Install All Workspace Recommended Extensions");
|
||||
static readonly ID = 'workbench.extensions.action.installRecommendedExtensions';
|
||||
static readonly LABEL = localize('installRecommendedExtensions', "Install Recommended Extensions");
|
||||
|
||||
private _recommendations: string[] = [];
|
||||
get recommendations(): string[] { return this._recommendations; }
|
||||
set recommendations(recommendations: string[]) { this._recommendations = recommendations; this.enabled = this._recommendations.length > 0; }
|
||||
|
||||
constructor(
|
||||
id: string = InstallWorkspaceRecommendedExtensionsAction.ID,
|
||||
label: string = InstallWorkspaceRecommendedExtensionsAction.LABEL,
|
||||
id: string,
|
||||
label: string,
|
||||
recommendations: string[],
|
||||
private readonly source: string,
|
||||
@IViewletService private readonly viewletService: IViewletService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
|
||||
@@ -1882,7 +1882,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action {
|
||||
viewlet.search('@recommended ');
|
||||
viewlet.focus();
|
||||
const names = this.recommendations;
|
||||
return this.extensionWorkbenchService.queryGallery({ names, source: 'install-all-workspace-recommendations' }, CancellationToken.None).then(pager => {
|
||||
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++) {
|
||||
@@ -1914,6 +1914,22 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class InstallWorkspaceRecommendedExtensionsAction extends InstallRecommendedExtensionsAction {
|
||||
|
||||
constructor(
|
||||
recommendations: string[],
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IExtensionsWorkbenchService extensionWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IProductService productService: IProductService,
|
||||
) {
|
||||
super('workbench.extensions.action.installWorkspaceRecommendedExtensions', localize('installWorkspaceRecommendedExtensions', "Install Workspace Recommended Extensions"), recommendations, 'install-all-workspace-recommendations',
|
||||
viewletService, instantiationService, extensionWorkbenchService, configurationService, extensionManagementServerService, productService);
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowRecommendedExtensionAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.extensions.action.showRecommendedExtension';
|
||||
@@ -2807,7 +2823,7 @@ export class SyncIgnoredIconAction extends ExtensionAction {
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
) {
|
||||
super('extensions.syncignore', '', SyncIgnoredIconAction.DISABLE_CLASS, false);
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectedKeys.includes('sync.ignoredExtensions'))(() => this.update()));
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectedKeys.includes('settingsSync.ignoredExtensions'))(() => this.update()));
|
||||
this.update();
|
||||
this.tooltip = localize('syncingore.label', "This extension is ignored during sync.");
|
||||
}
|
||||
|
||||
@@ -271,7 +271,8 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree<IExtensionData, IExte
|
||||
overrideStyles,
|
||||
accessibilityProvider: <IListAccessibilityProvider<IExtensionData>>{
|
||||
getAriaLabel(extensionData: IExtensionData): string {
|
||||
return localize('extension-arialabel', "{0}. Press enter for extension details.", extensionData.extension.displayName);
|
||||
const extension = extensionData.extension;
|
||||
return localize('extension-arialabel', "{0}, {1}, {2}, press enter for extension details.", extension.displayName, extension.version, extension.publisherDisplayName);
|
||||
},
|
||||
getWidgetAriaLabel(): string {
|
||||
return localize('extensions', "Extensions");
|
||||
|
||||
@@ -130,6 +130,9 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
|
||||
servers.push(this.extensionManagementServerService.remoteExtensionManagementServer);
|
||||
}
|
||||
if (servers.length === 0 && this.extensionManagementServerService.webExtensionManagementServer) {
|
||||
servers.push(this.extensionManagementServerService.webExtensionManagementServer);
|
||||
}
|
||||
const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => {
|
||||
return servers.length > 1 ? `${server.label} - ${viewTitle}` : viewTitle;
|
||||
};
|
||||
@@ -337,6 +340,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
private root: HTMLElement | undefined;
|
||||
private searchBox: SuggestEnabledInput | undefined;
|
||||
private readonly searchViewletState: MementoObject;
|
||||
private readonly sortActions: ChangeSortAction[];
|
||||
|
||||
constructor(
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@@ -384,6 +388,13 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
this.updateTitleArea();
|
||||
}
|
||||
}, this));
|
||||
|
||||
this.sortActions = [
|
||||
// this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Install Count"), this.onSearchChange, 'installs')), // {{SQL CARBON EDIT}}
|
||||
// this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Rating"), this.onSearchChange, 'rating')), // {{SQL CARBON EDIT}}
|
||||
this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Name"), this.onSearchChange, 'name')),
|
||||
this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.publishedDate', localize('sort by date', "Published Date"), this.onSearchChange, 'publishedDate')),
|
||||
];
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): void {
|
||||
@@ -518,12 +529,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
]);
|
||||
filterActions.push(...[
|
||||
new Separator(),
|
||||
new SubmenuAction('workbench.extensions.action.sortBy', localize('sorty by', "Sort By"), [
|
||||
// this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Install Count"), this.onSearchChange, 'installs'), // {{SQL CARBON EDIT}}
|
||||
// this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Rating"), this.onSearchChange, 'rating'), // {{SQL CARBON EDIT}}
|
||||
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Name"), this.onSearchChange, 'name'),
|
||||
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.publishedDate', localize('sort by date', "Published Date"), this.onSearchChange, 'publishedDate'),
|
||||
]),
|
||||
new SubmenuAction('workbench.extensions.action.sortBy', localize('sorty by', "Sort By"), this.sortActions),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ export class ExtensionsListView extends ViewPane {
|
||||
horizontalScrolling: false,
|
||||
accessibilityProvider: <IListAccessibilityProvider<IExtension | null>>{
|
||||
getAriaLabel(extension: IExtension | null): string {
|
||||
return extension ? localize('extension-arialabel', "{0}. Press enter for extension details.", extension.displayName) : '';
|
||||
return extension ? localize('extension-arialabel', "{0}, {1}, {2}, press enter for extension details.", extension.displayName, extension.version, extension.publisherDisplayName) : '';
|
||||
},
|
||||
getWidgetAriaLabel(): string {
|
||||
return localize('extensions', "Extensions");
|
||||
@@ -568,10 +568,11 @@ export class ExtensionsListView extends ViewPane {
|
||||
const configBasedRecommendationsPromise = this.tipsService.getConfigBasedRecommendations();
|
||||
const othersPromise = this.tipsService.getOtherRecommendations();
|
||||
const workspacePromise = this.tipsService.getWorkspaceRecommendations();
|
||||
const importantRecommendationsPromise = this.tipsService.getImportantRecommendations();
|
||||
|
||||
return Promise.all([othersPromise, workspacePromise, configBasedRecommendationsPromise])
|
||||
.then(([others, workspaceRecommendations, configBasedRecommendations]) => {
|
||||
const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, configBasedRecommendations, others, workspaceRecommendations);
|
||||
return Promise.all([othersPromise, workspacePromise, configBasedRecommendationsPromise, importantRecommendationsPromise])
|
||||
.then(([others, workspaceRecommendations, configBasedRecommendations, importantRecommendations]) => {
|
||||
const names = this.getTrimmedRecommendations(local, value, importantRecommendations, fileBasedRecommendations, configBasedRecommendations, others, workspaceRecommendations);
|
||||
const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason();
|
||||
/* __GDPR__
|
||||
"extensionAllRecommendations:open" : {
|
||||
@@ -624,14 +625,15 @@ export class ExtensionsListView extends ViewPane {
|
||||
const configBasedRecommendationsPromise = this.tipsService.getConfigBasedRecommendations();
|
||||
const othersPromise = this.tipsService.getOtherRecommendations();
|
||||
const workspacePromise = this.tipsService.getWorkspaceRecommendations();
|
||||
const importantRecommendationsPromise = this.tipsService.getImportantRecommendations();
|
||||
|
||||
return Promise.all([othersPromise, workspacePromise, configBasedRecommendationsPromise])
|
||||
.then(([others, workspaceRecommendations, configBasedRecommendations]) => {
|
||||
return Promise.all([othersPromise, workspacePromise, configBasedRecommendationsPromise, importantRecommendationsPromise])
|
||||
.then(([others, workspaceRecommendations, configBasedRecommendations, importantRecommendations]) => {
|
||||
configBasedRecommendations = configBasedRecommendations.filter(x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId));
|
||||
fileBasedRecommendations = fileBasedRecommendations.filter(x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId));
|
||||
others = others.filter(x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId));
|
||||
|
||||
const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, configBasedRecommendations, others, []);
|
||||
const names = this.getTrimmedRecommendations(local, value, importantRecommendations, fileBasedRecommendations, configBasedRecommendations, others, []);
|
||||
const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason();
|
||||
|
||||
/* __GDPR__
|
||||
@@ -725,22 +727,30 @@ export class ExtensionsListView extends ViewPane {
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
// Given all recommendations, trims and returns recommendations in the relevant order after filtering out installed extensions
|
||||
private getTrimmedRecommendations(installedExtensions: IExtension[], value: string, fileBasedRecommendations: IExtensionRecommendation[], configBasedRecommendations: IExtensionRecommendation[], otherRecommendations: IExtensionRecommendation[], workspaceRecommendations: IExtensionRecommendation[]): string[] {
|
||||
private getTrimmedRecommendations(installedExtensions: IExtension[], value: string, importantRecommendations: IExtensionRecommendation[], fileBasedRecommendations: IExtensionRecommendation[], configBasedRecommendations: IExtensionRecommendation[], otherRecommendations: IExtensionRecommendation[], workspaceRecommendations: IExtensionRecommendation[]): string[] {
|
||||
const totalCount = 10;
|
||||
workspaceRecommendations = workspaceRecommendations
|
||||
.filter(recommendation => {
|
||||
return !this.isRecommendationInstalled(recommendation, installedExtensions)
|
||||
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
|
||||
});
|
||||
configBasedRecommendations = configBasedRecommendations
|
||||
importantRecommendations = importantRecommendations
|
||||
.filter(recommendation => {
|
||||
return !this.isRecommendationInstalled(recommendation, installedExtensions)
|
||||
&& workspaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId)
|
||||
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
|
||||
});
|
||||
configBasedRecommendations = configBasedRecommendations
|
||||
.filter(recommendation => {
|
||||
return !this.isRecommendationInstalled(recommendation, installedExtensions)
|
||||
&& workspaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId)
|
||||
&& importantRecommendations.every(importantRecommendation => importantRecommendation.extensionId !== recommendation.extensionId)
|
||||
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
|
||||
});
|
||||
fileBasedRecommendations = fileBasedRecommendations.filter(recommendation => {
|
||||
return !this.isRecommendationInstalled(recommendation, installedExtensions)
|
||||
&& workspaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId)
|
||||
&& importantRecommendations.every(importantRecommendation => importantRecommendation.extensionId !== recommendation.extensionId)
|
||||
&& configBasedRecommendations.every(configBasedRecommendation => configBasedRecommendation.extensionId !== recommendation.extensionId)
|
||||
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
|
||||
});
|
||||
@@ -748,13 +758,14 @@ export class ExtensionsListView extends ViewPane {
|
||||
return !this.isRecommendationInstalled(recommendation, installedExtensions)
|
||||
&& fileBasedRecommendations.every(fileBasedRecommendation => fileBasedRecommendation.extensionId !== recommendation.extensionId)
|
||||
&& workspaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId)
|
||||
&& importantRecommendations.every(importantRecommendation => importantRecommendation.extensionId !== recommendation.extensionId)
|
||||
&& configBasedRecommendations.every(configBasedRecommendation => configBasedRecommendation.extensionId !== recommendation.extensionId)
|
||||
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
|
||||
});
|
||||
|
||||
const otherCount = Math.min(2, otherRecommendations.length);
|
||||
const fileBasedCount = Math.min(fileBasedRecommendations.length, totalCount - workspaceRecommendations.length - configBasedRecommendations.length - otherCount);
|
||||
const recommendations = [...workspaceRecommendations, ...configBasedRecommendations];
|
||||
const fileBasedCount = Math.min(fileBasedRecommendations.length, totalCount - workspaceRecommendations.length - importantRecommendations.length - configBasedRecommendations.length - otherCount);
|
||||
const recommendations = [...workspaceRecommendations, ...importantRecommendations, ...configBasedRecommendations];
|
||||
recommendations.push(...fileBasedRecommendations.splice(0, fileBasedCount));
|
||||
recommendations.push(...otherRecommendations.splice(0, otherCount));
|
||||
|
||||
@@ -1115,7 +1126,7 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
|
||||
|
||||
getActions(): IAction[] {
|
||||
if (!this.installAllAction) {
|
||||
this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, []));
|
||||
this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, []));
|
||||
this.installAllAction.class = 'codicon codicon-cloud-download';
|
||||
}
|
||||
|
||||
|
||||
@@ -1030,7 +1030,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
const id = extension.identifier.id.toLowerCase();
|
||||
|
||||
// first remove the extension completely from ignored extensions
|
||||
let currentValue = [...this.configurationService.getValue<string[]>('sync.ignoredExtensions')].map(id => id.toLowerCase());
|
||||
let currentValue = [...this.configurationService.getValue<string[]>('settingsSync.ignoredExtensions')].map(id => id.toLowerCase());
|
||||
currentValue = currentValue.filter(v => v !== id && v !== `-${id}`);
|
||||
|
||||
// If ignored, then add only if it is ignored by default
|
||||
@@ -1043,7 +1043,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
currentValue.push(id);
|
||||
}
|
||||
|
||||
return this.configurationService.updateValue('sync.ignoredExtensions', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER);
|
||||
return this.configurationService.updateValue('settingsSync.ignoredExtensions', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER);
|
||||
}
|
||||
|
||||
private installWithProgress<T>(installTask: () => Promise<T>, extensionName?: string): Promise<T> {
|
||||
|
||||
@@ -75,6 +75,14 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
get importantRecommendations(): ReadonlyArray<ExtensionRecommendation> {
|
||||
return this.recommendations.filter(e => this.importantExtensionTips[e.extensionId]);
|
||||
}
|
||||
|
||||
get otherRecommendations(): ReadonlyArray<ExtensionRecommendation> {
|
||||
return this.recommendations.filter(e => !this.importantExtensionTips[e.extensionId]);
|
||||
}
|
||||
|
||||
constructor(
|
||||
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@@ -226,7 +234,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
message = localize('reallyRecommendedExtensionPack', "The '{0}' extension pack is recommended for this file type.", extensionName);
|
||||
}
|
||||
|
||||
this.promptImportantExtensionInstallNotification(extensionId, message);
|
||||
this.promptImportantExtensionsInstallNotification([extensionId], message);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { localize } from 'vs/nls';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { InstallWorkspaceRecommendedExtensionsAction, ShowRecommendedExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
@@ -120,7 +120,7 @@ export class WorkspaceRecommendations extends ExtensionRecommendations {
|
||||
label: localize('installAll', "Install All"),
|
||||
run: () => {
|
||||
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'install' });
|
||||
const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All"), recommendations.map(({ extensionId }) => extensionId));
|
||||
const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, recommendations.map(({ extensionId }) => extensionId));
|
||||
installAllAction.run();
|
||||
installAllAction.dispose();
|
||||
c(undefined);
|
||||
|
||||
@@ -157,7 +157,7 @@ export class RuntimeExtensionsEditor extends BaseEditor {
|
||||
this._extensionService.getExtensions().then((extensions) => {
|
||||
// We only deal with extensions with source code!
|
||||
this._extensionsDescriptions = extensions.filter((extension) => {
|
||||
return !!extension.main;
|
||||
return Boolean(extension.main) || Boolean(extension.browser);
|
||||
});
|
||||
this._updateExtensions();
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
|
||||
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, SortBy
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionRecommendationsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionRecommendationsService, ExtensionRecommendationReason, IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
|
||||
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
|
||||
@@ -127,6 +127,9 @@ suite('ExtensionsListView Tests', () => {
|
||||
{ extensionId: configBasedRecommendationA.identifier.id }
|
||||
]);
|
||||
},
|
||||
getImportantRecommendations(): Promise<IExtensionRecommendation[]> {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
getFileBasedRecommendations() {
|
||||
return [
|
||||
{ extensionId: fileBasedRecommendationA.identifier.id },
|
||||
|
||||
Reference in New Issue
Block a user