Merge from vscode 2e5312cd61ff99c570299ecc122c52584265eda2

This commit is contained in:
ADS Merger
2020-04-23 02:50:35 +00:00
committed by Anthony Dresser
parent 3603f55d97
commit 7f1d8fc32f
659 changed files with 22709 additions and 12497 deletions

View File

@@ -0,0 +1,132 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExtensionTipsService, IExtensionManagementService, ILocalExtension, IConfigBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { localize } from 'vs/nls';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
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 { IWorkspaceContextService, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { distinct } from 'vs/base/common/arrays';
import { values } from 'vs/base/common/map';
export class ConfigBasedRecommendations extends ExtensionRecommendations {
private importantTips: IConfigBasedExtensionTip[] = [];
private otherTips: IConfigBasedExtensionTip[] = [];
private _recommendations: ExtensionRecommendation[] = [];
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
constructor(
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@INotificationService notificationService: INotificationService,
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService);
}
protected async doActivate(): Promise<void> {
await this.fetch();
this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(e => this.onWorkspaceFoldersChanged(e)));
this.promptWorkspaceRecommendations();
}
private async fetch(): Promise<void> {
const workspace = this.workspaceContextService.getWorkspace();
const importantTips: Map<string, IConfigBasedExtensionTip> = new Map<string, IConfigBasedExtensionTip>();
const otherTips: Map<string, IConfigBasedExtensionTip> = new Map<string, IConfigBasedExtensionTip>();
for (const folder of workspace.folders) {
const configBasedTips = await this.extensionTipsService.getConfigBasedTips(folder.uri);
for (const tip of configBasedTips) {
if (tip.important) {
importantTips.set(tip.extensionId, tip);
} else {
otherTips.set(tip.extensionId, tip);
}
}
}
this.importantTips = values(importantTips);
this.otherTips = values(otherTips).filter(tip => !importantTips.has(tip.extensionId));
this._recommendations = [...this.importantTips, ...this.otherTips].map(tip => this.toExtensionRecommendation(tip));
}
private async promptWorkspaceRecommendations(): Promise<void> {
if (this.hasToIgnoreRecommendationNotifications()) {
return;
}
if (this.importantTips.length === 0) {
return;
}
const local = await this.extensionManagementService.getInstalled(ExtensionType.User);
const { uninstalled } = this.groupByInstalled(distinct(this.importantTips.map(({ extensionId }) => extensionId)), local);
if (uninstalled.length === 0) {
return;
}
const importantExtensions = this.filterIgnoredOrNotAllowed(uninstalled);
if (importantExtensions.length === 0) {
return;
}
for (const extension of importantExtensions) {
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);
}
}
private groupByInstalled(recommendationsToSuggest: string[], local: ILocalExtension[]): { installed: string[], uninstalled: string[] } {
const installed: string[] = [], uninstalled: string[] = [];
const installedExtensionsIds = local.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set<string>());
recommendationsToSuggest.forEach(id => {
if (installedExtensionsIds.has(id.toLowerCase())) {
installed.push(id);
} else {
uninstalled.push(id);
}
});
return { installed, uninstalled };
}
private async onWorkspaceFoldersChanged(event: IWorkspaceFoldersChangeEvent): Promise<void> {
if (event.added.length) {
const oldImportantRecommended = this.importantTips;
await this.fetch();
// Suggest only if at least one of the newly added recommendations was not suggested before
if (this.importantTips.some(current => oldImportantRecommended.every(old => current.extensionId !== old.extensionId))) {
return this.promptWorkspaceRecommendations();
}
}
}
private toExtensionRecommendation(tip: IConfigBasedExtensionTip): ExtensionRecommendation {
return {
extensionId: tip.extensionId,
source: 'config',
reason: {
reasonId: ExtensionRecommendationReason.WorkspaceConfig,
reasonText: localize('exeBasedRecommendation', "This extension is recommended because of the current workspace configuration")
}
};
}
}

View File

@@ -0,0 +1,126 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IFileService } from 'vs/platform/files/common/files';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags';
import { isNumber } from 'vs/base/common/types';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
type DynamicWorkspaceRecommendationsClassification = {
count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
cache: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
type IStoredDynamicWorkspaceRecommendations = { recommendations: string[], timestamp: number };
const dynamicWorkspaceRecommendationsStorageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations';
const milliSecondsInADay = 1000 * 60 * 60 * 24;
export class DynamicWorkspaceRecommendations extends ExtensionRecommendations {
private _recommendations: ExtensionRecommendation[] = [];
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
constructor(
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
@IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IFileService private readonly fileService: IFileService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@INotificationService notificationService: INotificationService,
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService);
}
protected async doActivate(): Promise<void> {
await this.fetch();
this._register(this.contextService.onDidChangeWorkbenchState(() => this._recommendations = []));
}
/**
* Fetch extensions used by others on the same workspace as recommendations
*/
private async fetch(): Promise<void> {
this._register(this.contextService.onDidChangeWorkbenchState(() => this._recommendations = []));
if (this._recommendations.length
|| this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER
|| !this.fileService.canHandleResource(this.contextService.getWorkspace().folders[0].uri)
) {
return;
}
const folder = this.contextService.getWorkspace().folders[0];
const cachedDynamicWorkspaceRecommendations = this.getCachedDynamicWorkspaceRecommendations();
if (cachedDynamicWorkspaceRecommendations) {
this._recommendations = cachedDynamicWorkspaceRecommendations.map(id => this.toExtensionRecommendation(id, folder));
this.telemetryService.publicLog2<{ count: number, cache: number }, DynamicWorkspaceRecommendationsClassification>('dynamicWorkspaceRecommendations', { count: this._recommendations.length, cache: 1 });
return;
}
const [hashedRemotes1, hashedRemotes2] = await Promise.all([this.workspaceTagsService.getHashedRemotesFromUri(folder.uri, false), this.workspaceTagsService.getHashedRemotesFromUri(folder.uri, true)]);
const hashedRemotes = (hashedRemotes1 || []).concat(hashedRemotes2 || []);
if (!hashedRemotes.length) {
return;
}
const workspacesTips = await this.extensionTipsService.getAllWorkspacesTips();
if (!workspacesTips.length) {
return;
}
for (const hashedRemote of hashedRemotes) {
const workspaceTip = workspacesTips.filter(workspaceTip => isNonEmptyArray(workspaceTip.remoteSet) && workspaceTip.remoteSet.indexOf(hashedRemote) > -1)[0];
if (workspaceTip) {
this._recommendations = workspaceTip.recommendations.map(id => this.toExtensionRecommendation(id, folder));
this.storageService.store(dynamicWorkspaceRecommendationsStorageKey, JSON.stringify(<IStoredDynamicWorkspaceRecommendations>{ recommendations: workspaceTip.recommendations, timestamp: Date.now() }), StorageScope.WORKSPACE);
this.telemetryService.publicLog2<{ count: number, cache: number }, DynamicWorkspaceRecommendationsClassification>('dynamicWorkspaceRecommendations', { count: this._recommendations.length, cache: 0 });
return;
}
}
}
private getCachedDynamicWorkspaceRecommendations(): string[] | undefined {
try {
const storedDynamicWorkspaceRecommendations: IStoredDynamicWorkspaceRecommendations = JSON.parse(this.storageService.get(dynamicWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE, '{}'));
if (isNonEmptyArray(storedDynamicWorkspaceRecommendations.recommendations)
&& isNumber(storedDynamicWorkspaceRecommendations.timestamp)
&& storedDynamicWorkspaceRecommendations.timestamp > 0
&& (Date.now() - storedDynamicWorkspaceRecommendations.timestamp) / milliSecondsInADay < 14) {
return storedDynamicWorkspaceRecommendations.recommendations;
}
} catch (e) {
this.storageService.remove(dynamicWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE);
}
return undefined;
}
private toExtensionRecommendation(extensionId: string, folder: IWorkspaceFolder): ExtensionRecommendation {
return {
extensionId: extensionId.toLowerCase(),
source: 'dynamic',
reason: {
reasonId: ExtensionRecommendationReason.DynamicWorkspace,
reasonText: localize('dynamicWorkspaceRecommendation', "This extension may interest you because it's popular among users of the {0} repository.", folder.name)
}
};
}
}

View File

@@ -0,0 +1,120 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExtensionTipsService, IExecutableBasedExtensionTip, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { timeout } from 'vs/base/common/async';
import { localize } from 'vs/nls';
import { IStringDictionary } from 'vs/base/common/collections';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } 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';
type ExeExtensionRecommendationsClassification = {
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
exeName: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
};
export class ExeBasedRecommendations extends ExtensionRecommendations {
readonly _recommendations: ExtensionRecommendation[] = [];
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
constructor(
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@INotificationService notificationService: INotificationService,
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService);
/*
3s has come out to be the good number to fetch and prompt important exe based recommendations
Also fetch important exe based recommendations for reporting telemetry
*/
timeout(3000).then(() => this.fetchAndPromptImportantExeBasedRecommendations());
}
protected async doActivate(): Promise<void> {
const otherExectuableBasedTips = await this.extensionTipsService.getOtherExecutableBasedTips();
otherExectuableBasedTips.forEach(tip => this._recommendations.push(this.toExtensionRecommendation(tip)));
}
private async fetchAndPromptImportantExeBasedRecommendations(): Promise<void> {
const importantExeBasedRecommendations: IStringDictionary<IExecutableBasedExtensionTip> = {};
const importantExectuableBasedTips = await this.extensionTipsService.getImportantExecutableBasedTips();
importantExectuableBasedTips.forEach(tip => {
this._recommendations.push(this.toExtensionRecommendation(tip));
importantExeBasedRecommendations[tip.extensionId.toLowerCase()] = tip;
});
const local = await this.extensionManagementService.getInstalled(ExtensionType.User);
const { installed, uninstalled } = this.groupByInstalled(Object.keys(importantExeBasedRecommendations), local);
/* Log installed and uninstalled exe based recommendations */
for (const extensionId of installed) {
const tip = importantExeBasedRecommendations[extensionId];
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
}
for (const extensionId of uninstalled) {
const tip = importantExeBasedRecommendations[extensionId];
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:notInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
}
this.promptImportantExeBasedRecommendations(uninstalled, importantExeBasedRecommendations);
}
private promptImportantExeBasedRecommendations(recommendations: string[], importantExeBasedRecommendations: IStringDictionary<IExecutableBasedExtensionTip>): void {
if (this.hasToIgnoreRecommendationNotifications()) {
return;
}
recommendations = this.filterIgnoredOrNotAllowed(recommendations);
if (recommendations.length === 0) {
return;
}
const extensionId = recommendations[0];
const tip = importantExeBasedRecommendations[extensionId];
const message = localize('exeRecommended', "The '{0}' extension is recommended as you have {1} installed on your system.", tip.friendlyName!, tip.exeFriendlyName || basename(tip.windowsPath!));
this.promptImportantExtensionInstallNotification(extensionId, message);
}
private groupByInstalled(recommendationsToSuggest: string[], local: ILocalExtension[]): { installed: string[], uninstalled: string[] } {
const installed: string[] = [], uninstalled: string[] = [];
const installedExtensionsIds = local.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set<string>());
recommendationsToSuggest.forEach(id => {
if (installedExtensionsIds.has(id.toLowerCase())) {
installed.push(id);
} else {
uninstalled.push(id);
}
});
return { installed, uninstalled };
}
private toExtensionRecommendation(tip: IExecutableBasedExtensionTip): ExtensionRecommendation {
return {
extensionId: tip.extensionId.toLowerCase(),
source: 'executable',
reason: {
reasonId: ExtensionRecommendationReason.Executable,
reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", tip.friendlyName)
}
};
}
}

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
export class ExperimentalRecommendations extends ExtensionRecommendations {
private _recommendations: ExtensionRecommendation[] = [];
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
constructor(
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
@IExperimentService private readonly experimentService: IExperimentService,
@IConfigurationService configurationService: IConfigurationService,
@IInstantiationService instantiationService: IInstantiationService,
@INotificationService notificationService: INotificationService,
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService);
}
/**
* Fetch extensions used by others on the same workspace as recommendations
*/
protected async doActivate(): Promise<void> {
const experiments = await this.experimentService.getExperimentsByType(ExperimentActionType.AddToRecommendations);
for (const { action, state } of experiments) {
if (state === ExperimentState.Run && isNonEmptyArray(action?.properties?.recommendations) && action?.properties?.recommendationReason) {
action.properties.recommendations.forEach((extensionId: string) => this._recommendations.push({
extensionId: extensionId.toLowerCase(),
source: 'experimental',
reason: {
reasonId: ExtensionRecommendationReason.Experimental,
reasonText: action.properties.recommendationReason
}
}));
}
}
}
}

View File

@@ -316,10 +316,10 @@ export class ExtensionEditor extends BaseEditor {
}
async setInput(input: ExtensionsInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
await super.setInput(input, options, token);
if (this.template) {
await this.updateTemplate(input, this.template, !!options?.preserveFocus);
}
return super.setInput(input, options, token);
}
private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise<void> {
@@ -499,7 +499,7 @@ export class ExtensionEditor extends BaseEditor {
ignoreAction.enabled = true;
template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
show(template.subtextContainer);
} else if (this.extensionRecommendationsService.getAllIgnoredRecommendations().global.indexOf(extension.identifier.id.toLowerCase()) !== -1) {
} else if (this.extensionRecommendationsService.getIgnoredRecommendations().indexOf(extension.identifier.id.toLowerCase()) !== -1) {
undoIgnoreAction.enabled = true;
template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
show(template.subtextContainer);

View File

@@ -0,0 +1,133 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { localize } from 'vs/nls';
import { InstallRecommendedExtensionAction, ShowRecommendedExtensionsAction } 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';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
type ExtensionRecommendationsNotificationClassification = {
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
};
const ignoreImportantExtensionRecommendation = 'extensionsAssistant/importantRecommendationsIgnore';
const choiceNever = localize('neverShowAgain', "Don't Show Again");
export type ExtensionRecommendation = {
readonly extensionId: string,
readonly source: ExtensionRecommendationSource;
readonly reason: IExtensionRecommendationReson;
};
export abstract class ExtensionRecommendations extends Disposable {
readonly abstract recommendations: ReadonlyArray<ExtensionRecommendation>;
protected abstract doActivate(): Promise<void>;
constructor(
protected readonly isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
@IInstantiationService protected readonly instantiationService: IInstantiationService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@INotificationService protected readonly notificationService: INotificationService,
@ITelemetryService protected readonly telemetryService: ITelemetryService,
@IStorageService protected readonly storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super();
storageKeysSyncRegistryService.registerStorageKey({ key: ignoreImportantExtensionRecommendation, version: 1 });
}
private _activationPromise: Promise<void> | null = null;
get activated(): boolean { return this._activationPromise !== null; }
activate(): Promise<void> {
if (!this._activationPromise) {
this._activationPromise = this.doActivate();
}
return this._activationPromise;
}
protected promptImportantExtensionInstallNotification(extensionId: 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.instantiationService.createInstance(InstallRecommendedExtensionAction, extensionId).run();
}
}, {
label: localize('showRecommendations', "Show Recommendations"),
run: () => {
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId });
const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations"));
recommendationsAction.run();
recommendationsAction.dispose();
}
}, {
label: choiceNever,
isSecondary: true,
run: () => {
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?"),
[{
label: localize('ignoreAll', "Yes, Ignore All"),
run: () => this.setIgnoreRecommendationsConfig(true)
}, {
label: localize('no', "No"),
run: () => this.setIgnoreRecommendationsConfig(false)
}]
);
}
}],
{
sticky: true,
onCancel: () => {
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId });
}
}
);
}
protected hasToIgnoreRecommendationNotifications(): boolean {
const config = this.configurationService.getValue<IExtensionsConfiguration>(ConfigurationKey);
return config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand;
}
protected filterIgnoredOrNotAllowed(recommendationsToSuggest: string[]): string[] {
const importantRecommendationsIgnoreList = (<string[]>JSON.parse(this.storageService.get(ignoreImportantExtensionRecommendation, StorageScope.GLOBAL, '[]'))).map(e => e.toLowerCase());
return recommendationsToSuggest.filter(id => {
if (importantRecommendationsIgnoreList.indexOf(id) !== -1) {
return false;
}
if (!this.isExtensionAllowedToBeRecommended(id)) {
return false;
}
return true;
});
}
private addToImportantRecommendationsIgnore(id: string) {
const importantRecommendationsIgnoreList = <string[]>JSON.parse(this.storageService.get(ignoreImportantExtensionRecommendation, StorageScope.GLOBAL, '[]'));
importantRecommendationsIgnoreList.push(id.toLowerCase());
this.storageService.store(ignoreImportantExtensionRecommendation, JSON.stringify(importantRecommendationsIgnoreList), StorageScope.GLOBAL);
}
private setIgnoreRecommendationsConfig(configVal: boolean) {
this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER);
}
}

View File

@@ -85,75 +85,76 @@ Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegis
icon: 'codicon-extensions',
order: 14, // {{SQL CARBON EDIT}}
rejectAddedViews: true,
alwaysUseContainerInfo: true
}, ViewContainerLocation.Sidebar);
// Global actions
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
const openViewletActionDescriptor = SyncActionDescriptor.create(OpenExtensionsViewletAction, OpenExtensionsViewletAction.ID, OpenExtensionsViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X });
const openViewletActionDescriptor = SyncActionDescriptor.from(OpenExtensionsViewletAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X });
actionRegistry.registerWorkbenchAction(openViewletActionDescriptor, 'View: Show Extensions', localize('view', "View"));
const installActionDescriptor = SyncActionDescriptor.create(InstallExtensionsAction, InstallExtensionsAction.ID, InstallExtensionsAction.LABEL);
const installActionDescriptor = SyncActionDescriptor.from(InstallExtensionsAction);
actionRegistry.registerWorkbenchAction(installActionDescriptor, 'Extensions: Install Extensions', ExtensionsLabel);
const listOutdatedActionDescriptor = SyncActionDescriptor.create(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL);
const listOutdatedActionDescriptor = SyncActionDescriptor.from(ShowOutdatedExtensionsAction);
actionRegistry.registerWorkbenchAction(listOutdatedActionDescriptor, 'Extensions: Show Outdated Extensions', ExtensionsLabel);
const recommendationsActionDescriptor = SyncActionDescriptor.create(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL);
const recommendationsActionDescriptor = SyncActionDescriptor.from(ShowRecommendedExtensionsAction);
actionRegistry.registerWorkbenchAction(recommendationsActionDescriptor, 'Extensions: Show Recommended Extensions', ExtensionsLabel);
const keymapRecommendationsActionDescriptor = SyncActionDescriptor.create(ShowRecommendedKeymapExtensionsAction, ShowRecommendedKeymapExtensionsAction.ID, ShowRecommendedKeymapExtensionsAction.SHORT_LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_M) });
const keymapRecommendationsActionDescriptor = SyncActionDescriptor.from(ShowRecommendedKeymapExtensionsAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_M) });
actionRegistry.registerWorkbenchAction(keymapRecommendationsActionDescriptor, 'Preferences: Keymaps', PreferencesLabel);
const languageExtensionsActionDescriptor = SyncActionDescriptor.create(ShowLanguageExtensionsAction, ShowLanguageExtensionsAction.ID, ShowLanguageExtensionsAction.SHORT_LABEL);
const languageExtensionsActionDescriptor = SyncActionDescriptor.from(ShowLanguageExtensionsAction);
actionRegistry.registerWorkbenchAction(languageExtensionsActionDescriptor, 'Preferences: Language Extensions', PreferencesLabel);
const azureExtensionsActionDescriptor = SyncActionDescriptor.create(ShowAzureExtensionsAction, ShowAzureExtensionsAction.ID, ShowAzureExtensionsAction.SHORT_LABEL);
const azureExtensionsActionDescriptor = SyncActionDescriptor.from(ShowAzureExtensionsAction);
actionRegistry.registerWorkbenchAction(azureExtensionsActionDescriptor, 'Preferences: Azure Extensions', PreferencesLabel);
const popularActionDescriptor = SyncActionDescriptor.create(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL);
const popularActionDescriptor = SyncActionDescriptor.from(ShowPopularExtensionsAction);
actionRegistry.registerWorkbenchAction(popularActionDescriptor, 'Extensions: Show Popular Extensions', ExtensionsLabel);
const enabledActionDescriptor = SyncActionDescriptor.create(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL);
const enabledActionDescriptor = SyncActionDescriptor.from(ShowEnabledExtensionsAction);
actionRegistry.registerWorkbenchAction(enabledActionDescriptor, 'Extensions: Show Enabled Extensions', ExtensionsLabel);
const installedActionDescriptor = SyncActionDescriptor.create(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL);
const installedActionDescriptor = SyncActionDescriptor.from(ShowInstalledExtensionsAction);
actionRegistry.registerWorkbenchAction(installedActionDescriptor, 'Extensions: Show Installed Extensions', ExtensionsLabel);
const disabledActionDescriptor = SyncActionDescriptor.create(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL);
const disabledActionDescriptor = SyncActionDescriptor.from(ShowDisabledExtensionsAction);
actionRegistry.registerWorkbenchAction(disabledActionDescriptor, 'Extensions: Show Disabled Extensions', ExtensionsLabel);
const builtinActionDescriptor = SyncActionDescriptor.create(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL);
const builtinActionDescriptor = SyncActionDescriptor.from(ShowBuiltInExtensionsAction);
actionRegistry.registerWorkbenchAction(builtinActionDescriptor, 'Extensions: Show Built-in Extensions', ExtensionsLabel);
const updateAllActionDescriptor = SyncActionDescriptor.create(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL);
const updateAllActionDescriptor = SyncActionDescriptor.from(UpdateAllAction);
actionRegistry.registerWorkbenchAction(updateAllActionDescriptor, 'Extensions: Update All Extensions', ExtensionsLabel);
if (InstallVSIXAction.AVAILABLE) { // {{SQL CARBON EDIT}} add disable logic
const installVSIXActionDescriptor = SyncActionDescriptor.create(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL);
const installVSIXActionDescriptor = SyncActionDescriptor.from(InstallVSIXAction);
actionRegistry.registerWorkbenchAction(installVSIXActionDescriptor, 'Extensions: Install from VSIX...', ExtensionsLabel);
}
const disableAllAction = SyncActionDescriptor.create(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL);
const disableAllAction = SyncActionDescriptor.from(DisableAllAction);
actionRegistry.registerWorkbenchAction(disableAllAction, 'Extensions: Disable All Installed Extensions', ExtensionsLabel);
const disableAllWorkspaceAction = SyncActionDescriptor.create(DisableAllWorkspaceAction, DisableAllWorkspaceAction.ID, DisableAllWorkspaceAction.LABEL);
const disableAllWorkspaceAction = SyncActionDescriptor.from(DisableAllWorkspaceAction);
actionRegistry.registerWorkbenchAction(disableAllWorkspaceAction, 'Extensions: Disable All Installed Extensions for this Workspace', ExtensionsLabel);
const enableAllAction = SyncActionDescriptor.create(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL);
const enableAllAction = SyncActionDescriptor.from(EnableAllAction);
actionRegistry.registerWorkbenchAction(enableAllAction, 'Extensions: Enable All Extensions', ExtensionsLabel);
const enableAllWorkspaceAction = SyncActionDescriptor.create(EnableAllWorkspaceAction, EnableAllWorkspaceAction.ID, EnableAllWorkspaceAction.LABEL);
const enableAllWorkspaceAction = SyncActionDescriptor.from(EnableAllWorkspaceAction);
actionRegistry.registerWorkbenchAction(enableAllWorkspaceAction, 'Extensions: Enable All Extensions for this Workspace', ExtensionsLabel);
const checkForUpdatesAction = SyncActionDescriptor.create(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL);
const checkForUpdatesAction = SyncActionDescriptor.from(CheckForUpdatesAction);
actionRegistry.registerWorkbenchAction(checkForUpdatesAction, `Extensions: Check for Extension Updates`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL), `Extensions: Enable Auto Updating Extensions`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL), `Extensions: Disable Auto Updating Extensions`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(InstallSpecificVersionOfExtensionAction, InstallSpecificVersionOfExtensionAction.ID, InstallSpecificVersionOfExtensionAction.LABEL), 'Install Specific Version of Extension...', ExtensionsLabel);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ReinstallAction, ReinstallAction.ID, ReinstallAction.LABEL), 'Reinstall Extension...', localize('developer', "Developer"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(EnableAutoUpdateAction), `Extensions: Enable Auto Updating Extensions`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(DisableAutoUpdateAction), `Extensions: Disable Auto Updating Extensions`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(InstallSpecificVersionOfExtensionAction), 'Install Specific Version of Extension...', ExtensionsLabel);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ReinstallAction), 'Reinstall Extension...', localize('developer', "Developer"));
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({

View File

@@ -16,7 +16,7 @@ import { dispose, Disposable } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
import { IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionRecommendationsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionRecommendationsService, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}}
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
@@ -59,6 +59,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
import { IProductService } from 'vs/platform/product/common/productService';
import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { Codicon } from 'vs/base/common/codicons';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; // {{SQL CARBON EDIT}}
import product from 'vs/platform/product/common/product';
@@ -1718,14 +1719,14 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action {
static readonly ID = 'workbench.extensions.action.installWorkspaceRecommendedExtensions';
static readonly LABEL = localize('installWorkspaceRecommendedExtensions', "Install All Workspace Recommended Extensions");
private _recommendations: IExtensionRecommendation[] = [];
get recommendations(): IExtensionRecommendation[] { return this._recommendations; }
set recommendations(recommendations: IExtensionRecommendation[]) { this._recommendations = recommendations; this.enabled = this._recommendations.length > 0; }
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,
recommendations: IExtensionRecommendation[],
recommendations: string[],
@IViewletService private readonly viewletService: IViewletService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
@@ -1743,7 +1744,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action {
.then(viewlet => {
viewlet.search('@recommended ');
viewlet.focus();
const names = this.recommendations.map(({ extensionId }) => extensionId);
const names = this.recommendations;
return this.extensionWorkbenchService.queryGallery({ names, source: 'install-all-workspace-recommendations' }, CancellationToken.None).then(pager => {
let installPromises: Promise<any>[] = [];
let model = new PagedModel(pager);
@@ -1864,7 +1865,7 @@ export class UndoIgnoreExtensionRecommendationAction extends Action {
export class ShowRecommendedKeymapExtensionsAction extends Action {
static readonly ID = 'workbench.extensions.action.showRecommendedKeymapExtensions';
static readonly SHORT_LABEL = localize('showRecommendedKeymapExtensionsShort', "Keymaps");
static readonly LABEL = localize('showRecommendedKeymapExtensionsShort', "Keymaps");
constructor(
id: string,
@@ -1887,7 +1888,7 @@ export class ShowRecommendedKeymapExtensionsAction extends Action {
export class ShowLanguageExtensionsAction extends Action {
static readonly ID = 'workbench.extensions.action.showLanguageExtensions';
static readonly SHORT_LABEL = localize('showLanguageExtensionsShort', "Language Extensions");
static readonly LABEL = localize('showLanguageExtensionsShort', "Language Extensions");
constructor(
id: string,
@@ -1910,7 +1911,7 @@ export class ShowLanguageExtensionsAction extends Action {
export class ShowAzureExtensionsAction extends Action {
static readonly ID = 'workbench.extensions.action.showAzureExtensions';
static readonly SHORT_LABEL = localize('showAzureExtensionsShort', "Azure Extensions");
static readonly LABEL = localize('showAzureExtensionsShort', "Azure Extensions");
constructor(
id: string,
@@ -2712,8 +2713,8 @@ export class ExtensionToolTipAction extends ExtensionAction {
export class SystemDisabledWarningAction extends ExtensionAction {
private static readonly CLASS = `${ExtensionAction.ICON_ACTION_CLASS} system-disable`;
private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} codicon-warning`;
private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} codicon-info`;
private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} ${Codicon.warning.classNames}`;
private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} ${Codicon.info.classNames}`;
updateWhenCounterExtensionChanges: boolean = true;
private _runningExtensions: IExtensionDescription[] | null = null;
@@ -2754,8 +2755,8 @@ export class SystemDisabledWarningAction extends ExtensionAction {
if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) {
this.class = `${SystemDisabledWarningAction.INFO_CLASS}`;
this.tooltip = this.extension.server === this.extensionManagementServerService.localExtensionManagementServer
? localize('Install language pack also in remote server', "Install the language pack extension on '{0}' to enable it also there.", this.extensionManagementServerService.remoteExtensionManagementServer.label)
: localize('Install language pack also locally', "Install the language pack extension locally to enable it also there.");
? localize('Install language pack also in remote server', "Install the language pack extension on '{0}' to enable it there also.", this.extensionManagementServerService.remoteExtensionManagementServer.label)
: localize('Install language pack also locally', "Install the language pack extension locally to enable it there also.");
}
return;
}

View File

@@ -27,6 +27,7 @@ import { listFocusForeground, listFocusBackground } from 'vs/platform/theme/comm
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
export class ExtensionsGridView extends Disposable {
@@ -267,7 +268,15 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree<IExtensionData, IExte
indent: 40,
identityProvider,
multipleSelectionSupport: false,
overrideStyles
overrideStyles,
accessibilityProvider: <IListAccessibilityProvider<IExtensionData>>{
getAriaLabel(extensionData: IExtensionData): string {
return localize('extension-arialabel', "{0}. Press enter for extension details.", extensionData.extension.displayName);
},
getWidgetAriaLabel(): string {
return localize('extensions', "Extensions");
}
}
},
contextKeyService, listService, themeService, configurationService, keybindingService, accessibilityService
);

View File

@@ -34,7 +34,7 @@ import Severity from 'vs/base/common/severity';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views';
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
@@ -44,7 +44,6 @@ import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views';
import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery';
import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput';
@@ -94,8 +93,9 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
constructor(
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@ILabelService private readonly labelService: ILabelService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService
) {
this.container = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).get(VIEWLET_ID)!;
this.container = viewDescriptorService.getViewContainerById(VIEWLET_ID)!;
this.registerViews();
}
@@ -363,7 +363,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IPreferencesService private readonly preferencesService: IPreferencesService
) {
super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
this.searchDelayer = new Delayer(500);
this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService);

View File

@@ -28,7 +28,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction, getContextMenuActions } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { WorkbenchPagedList, ResourceNavigator } from 'vs/platform/list/browser/listService';
import { WorkbenchPagedList, ListResourceNavigator } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
@@ -50,6 +50,7 @@ import { IMenuService } from 'vs/platform/actions/common/actions';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
// Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions
const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result'];
@@ -136,10 +137,17 @@ export class ExtensionsListView extends ViewPane {
const extensionsViewState = new ExtensionsViewState();
const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState);
this.list = this.instantiationService.createInstance<typeof WorkbenchPagedList, WorkbenchPagedList<IExtension>>(WorkbenchPagedList, 'Extensions', extensionsList, delegate, [renderer], {
ariaLabel: localize('extensions', "Extensions"),
multipleSelectionSupport: false,
setRowLineHeight: false,
horizontalScrolling: false,
accessibilityProvider: <IListAccessibilityProvider<IExtension | null>>{
getAriaLabel(extension: IExtension | null): string {
return extension ? localize('extension-arialabel', "{0}. Press enter for extension details.", extension.displayName) : '';
},
getWidgetAriaLabel(): string {
return localize('extensions', "Extensions");
}
},
overrideStyles: {
listBackground: SIDE_BAR_BACKGROUND
}
@@ -149,7 +157,7 @@ export class ExtensionsListView extends ViewPane {
this._register(this.list);
this._register(extensionsViewState);
const resourceNavigator = this._register(ResourceNavigator.createListResourceNavigator(this.list, { openOnFocus: false, openOnSelection: true, openOnSingleClick: true }));
const resourceNavigator = this._register(new ListResourceNavigator(this.list, { openOnFocus: false, openOnSelection: true, openOnSingleClick: true }));
this._register(Event.debounce(Event.filter(resourceNavigator.onDidOpenResource, e => e.element !== null), (last, event) => event, 75, true)(options => {
this.openExtension(this.list!.model.get(options.element!), { sideByside: options.sideBySide, ...options.editorOptions });
}));
@@ -168,6 +176,7 @@ export class ExtensionsListView extends ViewPane {
}
protected layoutBody(height: number, width: number): void {
super.layoutBody(height, width);
if (this.bodyTemplate) {
this.bodyTemplate.extensionsList.style.height = height + 'px';
}
@@ -563,12 +572,13 @@ export class ExtensionsListView extends ViewPane {
.then(result => result.filter(e => e.type === ExtensionType.User))
.then(local => {
const fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
const configBasedRecommendationsPromise = this.tipsService.getConfigBasedRecommendations();
const othersPromise = this.tipsService.getOtherRecommendations();
const workspacePromise = this.tipsService.getWorkspaceRecommendations();
return Promise.all([othersPromise, workspacePromise])
.then(([others, workspaceRecommendations]) => {
const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, others, workspaceRecommendations);
return Promise.all([othersPromise, workspacePromise, configBasedRecommendationsPromise])
.then(([others, workspaceRecommendations, configBasedRecommendations]) => {
const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, configBasedRecommendations, others, workspaceRecommendations);
const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason();
/* __GDPR__
"extensionAllRecommendations:open" : {
@@ -618,15 +628,17 @@ export class ExtensionsListView extends ViewPane {
.then(result => result.filter(e => e.type === ExtensionType.User))
.then(local => {
let fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
const configBasedRecommendationsPromise = this.tipsService.getConfigBasedRecommendations();
const othersPromise = this.tipsService.getOtherRecommendations();
const workspacePromise = this.tipsService.getWorkspaceRecommendations();
return Promise.all([othersPromise, workspacePromise])
.then(([others, workspaceRecommendations]) => {
return Promise.all([othersPromise, workspacePromise, configBasedRecommendationsPromise])
.then(([others, workspaceRecommendations, configBasedRecommendations]) => {
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, others, []);
const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, configBasedRecommendations, others, []);
const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason();
/* __GDPR__
@@ -720,28 +732,36 @@ 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[], otherRecommendations: IExtensionRecommendation[], workspaceRecommendations: IExtensionRecommendation[]): string[] {
const totalCount = 8;
private getTrimmedRecommendations(installedExtensions: IExtension[], value: string, 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
.filter(recommendation => {
return !this.isRecommendationInstalled(recommendation, installedExtensions)
&& workspaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.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)
&& configBasedRecommendations.every(configBasedRecommendation => configBasedRecommendation.extensionId !== recommendation.extensionId)
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
});
otherRecommendations = otherRecommendations.filter(recommendation => {
return !this.isRecommendationInstalled(recommendation, installedExtensions)
&& fileBasedRecommendations.every(fileBasedRecommendation => fileBasedRecommendation.extensionId !== recommendation.extensionId)
&& workspaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.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 - otherCount);
const recommendations = workspaceRecommendations;
const fileBasedCount = Math.min(fileBasedRecommendations.length, totalCount - workspaceRecommendations.length - configBasedRecommendations.length - otherCount);
const recommendations = [...workspaceRecommendations, ...configBasedRecommendations];
recommendations.push(...fileBasedRecommendations.splice(0, fileBasedCount));
recommendations.push(...otherRecommendations.splice(0, otherCount));
@@ -1108,7 +1128,7 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
private async setRecommendationsToInstall(): Promise<void> {
const recommendations = await this.getRecommendationsToInstall();
if (this.installAllAction) {
this.installAllAction.recommendations = recommendations;
this.installAllAction.recommendations = recommendations.map(({ extensionId }) => extensionId);
}
}

View File

@@ -159,12 +159,7 @@ export class TooltipWidget extends ExtensionWidget {
}
render(): void {
this.parent.title = '';
this.parent.removeAttribute('aria-label');
this.parent.title = this.getTooltip();
if (this.extension) {
this.parent.setAttribute('aria-label', localize('extension-arialabel', "{0}. Press enter for extension details.", this.extension.displayName));
}
}
private getTooltip(): string {
@@ -208,7 +203,6 @@ export class RecommendationWidget extends ExtensionWidget {
private clear(): void {
this.tooltip = '';
this.parent.setAttribute('aria-label', this.extension ? localize('viewExtensionDetailsAria', "{0}. Press enter for extension details.", this.extension.displayName) : '');
if (this.element) {
this.parent.removeChild(this.element);
}

View File

@@ -0,0 +1,309 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ExtensionRecommendationSource, ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IProductService } from 'vs/platform/product/common/productService';
import { forEach, IStringDictionary } from 'vs/base/common/collections';
import { ITextModel } from 'vs/editor/common/model';
import { Schemas } from 'vs/base/common/network';
import { extname } from 'vs/base/common/resources';
import { match } from 'vs/base/common/glob';
import { URI } from 'vs/base/common/uri';
import { MIME_UNKNOWN, guessMimeTypes } from 'vs/base/common/mime';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { setImmediate } from 'vs/base/common/platform';
type FileExtensionSuggestionClassification = {
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
fileExtension: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
};
const recommendationsStorageKey = 'extensionsAssistant/recommendations';
const searchMarketplace = localize('searchMarketplace', "Search Marketplace");
const milliSecondsInADay = 1000 * 60 * 60 * 24;
const processedFileExtensions: string[] = [];
export class FileBasedRecommendations extends ExtensionRecommendations {
private readonly extensionTips: IStringDictionary<string> = Object.create(null);
private readonly importantExtensionTips: IStringDictionary<{ name: string; pattern: string; isExtensionPack?: boolean }> = Object.create(null);
private fileBasedRecommendationsByPattern: IStringDictionary<string[]> = Object.create(null);
private fileBasedRecommendations: IStringDictionary<{ recommendedTime: number, sources: ExtensionRecommendationSource[] }> = Object.create(null);
get recommendations(): ReadonlyArray<ExtensionRecommendation> {
const recommendations: ExtensionRecommendation[] = [];
Object.keys(this.fileBasedRecommendations)
.sort((a, b) => {
if (this.fileBasedRecommendations[a].recommendedTime === this.fileBasedRecommendations[b].recommendedTime) {
if (this.importantExtensionTips[a]) {
return -1;
}
if (this.importantExtensionTips[b]) {
return 1;
}
}
return this.fileBasedRecommendations[a].recommendedTime > this.fileBasedRecommendations[b].recommendedTime ? -1 : 1;
})
.forEach(extensionId => {
for (const source of this.fileBasedRecommendations[extensionId].sources) {
recommendations.push({
extensionId,
source,
reason: {
reasonId: ExtensionRecommendationReason.File,
reasonText: localize('fileBasedRecommendation', "This extension is recommended based on the files you recently opened.")
}
});
}
});
return recommendations;
}
constructor(
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionService private readonly extensionService: IExtensionService,
@IViewletService private readonly viewletService: IViewletService,
@IModelService private readonly modelService: IModelService,
@IProductService productService: IProductService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@INotificationService notificationService: INotificationService,
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService);
if (productService.extensionTips) {
forEach(productService.extensionTips, ({ key, value }) => this.extensionTips[key.toLowerCase()] = value);
}
if (productService.extensionImportantTips) {
forEach(productService.extensionImportantTips, ({ key, value }) => this.importantExtensionTips[key.toLowerCase()] = value);
}
}
protected async doActivate(): Promise<void> {
const allRecommendations: string[] = [];
// group extension recommendations by pattern, like {**/*.md} -> [ext.foo1, ext.bar2]
forEach(this.extensionTips, ({ key: extensionId, value: pattern }) => {
const ids = this.fileBasedRecommendationsByPattern[pattern] || [];
ids.push(extensionId);
this.fileBasedRecommendationsByPattern[pattern] = ids;
allRecommendations.push(extensionId);
});
forEach(this.importantExtensionTips, ({ key: extensionId, value }) => {
const ids = this.fileBasedRecommendationsByPattern[value.pattern] || [];
ids.push(extensionId);
this.fileBasedRecommendationsByPattern[value.pattern] = ids;
allRecommendations.push(extensionId);
});
const cachedRecommendations = this.getCachedRecommendations();
const now = Date.now();
// Retire existing recommendations if they are older than a week or are not part of this.productService.extensionTips anymore
forEach(cachedRecommendations, ({ key, value }) => {
const diff = (now - value) / milliSecondsInADay;
if (diff <= 7 && allRecommendations.indexOf(key) > -1) {
this.fileBasedRecommendations[key] = { recommendedTime: value, sources: ['cached'] };
}
});
this._register(this.modelService.onModelAdded(this.promptRecommendationsForModel, this));
this.modelService.getModels().forEach(model => this.promptRecommendationsForModel(model));
}
/**
* Prompt the user to either install the recommended extension for the file type in the current editor model
* or prompt to search the marketplace if it has extensions that can support the file type
*/
private promptRecommendationsForModel(model: ITextModel): void {
const uri = model.uri;
const supportedSchemes = [Schemas.untitled, Schemas.file, Schemas.vscodeRemote];
if (!uri || supportedSchemes.indexOf(uri.scheme) === -1) {
return;
}
let fileExtension = extname(uri);
if (fileExtension) {
if (processedFileExtensions.indexOf(fileExtension) > -1) {
return;
}
processedFileExtensions.push(fileExtension);
}
// re-schedule this bit of the operation to be off the critical path - in case glob-match is slow
setImmediate(() => this.promptRecommendations(uri, fileExtension));
}
private async promptRecommendations(uri: URI, fileExtension: string): Promise<void> {
const recommendationsToPrompt: string[] = [];
forEach(this.fileBasedRecommendationsByPattern, ({ key: pattern, value: extensionIds }) => {
if (match(pattern, uri.toString())) {
for (const extensionId of extensionIds) {
// Add to recommendation to prompt if it is an important tip
if (this.importantExtensionTips[extensionId]) {
recommendationsToPrompt.push(extensionId);
}
// Update file based recommendations
const filedBasedRecommendation = this.fileBasedRecommendations[extensionId] || { recommendedTime: Date.now(), sources: [] };
filedBasedRecommendation.recommendedTime = Date.now();
if (!filedBasedRecommendation.sources.some(s => s instanceof URI && s.toString() === uri.toString())) {
filedBasedRecommendation.sources.push(uri);
}
this.fileBasedRecommendations[extensionId.toLowerCase()] = filedBasedRecommendation;
}
}
});
this.storeCachedRecommendations();
if (this.hasToIgnoreRecommendationNotifications()) {
return;
}
const installed = await this.extensionManagementService.getInstalled(ExtensionType.User);
if (await this.promptRecommendedExtensionForFileType(recommendationsToPrompt, installed)) {
return;
}
if (fileExtension) {
fileExtension = fileExtension.substr(1); // Strip the dot
}
if (!fileExtension) {
return;
}
await this.extensionService.whenInstalledExtensionsRegistered();
const mimeTypes = guessMimeTypes(uri);
if (mimeTypes.length !== 1 || mimeTypes[0] !== MIME_UNKNOWN) {
return;
}
this.promptRecommendedExtensionForFileExtension(fileExtension, installed);
}
private async promptRecommendedExtensionForFileType(recommendations: string[], installed: ILocalExtension[]): Promise<boolean> {
recommendations = this.filterIgnoredOrNotAllowed(recommendations);
if (recommendations.length === 0) {
return false;
}
recommendations = this.filterInstalled(recommendations, installed);
if (recommendations.length === 0) {
return false;
}
const extensionId = recommendations[0];
const entry = this.importantExtensionTips[extensionId];
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.promptImportantExtensionInstallNotification(extensionId, message);
return true;
}
private async promptRecommendedExtensionForFileExtension(fileExtension: string, installed: ILocalExtension[]): Promise<void> {
const fileExtensionSuggestionIgnoreList = <string[]>JSON.parse(this.storageService.get('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]'));
if (fileExtensionSuggestionIgnoreList.indexOf(fileExtension) > -1) {
return;
}
const text = `ext:${fileExtension}`;
const pager = await this.extensionsWorkbenchService.queryGallery({ text, pageSize: 100 }, CancellationToken.None);
if (pager.firstPage.length === 0) {
return;
}
const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set<string>());
if (pager.firstPage.some(e => installedExtensionsIds.has(e.identifier.id.toLowerCase()))) {
return;
}
this.notificationService.prompt(
Severity.Info,
localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension),
[{
label: searchMarketplace,
run: () => {
this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'ok', fileExtension });
this.viewletService.openViewlet('workbench.view.extensions', true)
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
.then(viewlet => {
viewlet.search(`ext:${fileExtension}`);
viewlet.focus();
});
}
}, {
label: localize('dontShowAgainExtension', "Don't Show Again for '.{0}' files", fileExtension),
run: () => {
fileExtensionSuggestionIgnoreList.push(fileExtension);
this.storageService.store(
'extensionsAssistant/fileExtensionsSuggestionIgnore',
JSON.stringify(fileExtensionSuggestionIgnoreList),
StorageScope.GLOBAL
);
this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension });
}
}],
{
sticky: true,
onCancel: () => {
this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension });
}
}
);
}
private filterInstalled(recommendationsToSuggest: string[], installed: ILocalExtension[]): string[] {
const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set<string>());
return recommendationsToSuggest.filter(id => !installedExtensionsIds.has(id.toLowerCase()));
}
private getCachedRecommendations(): IStringDictionary<number> {
let storedRecommendations = JSON.parse(this.storageService.get(recommendationsStorageKey, StorageScope.GLOBAL, '[]'));
if (Array.isArray<string>(storedRecommendations)) {
storedRecommendations = storedRecommendations.reduce((result, id) => { result[id] = Date.now(); return result; }, <IStringDictionary<number>>{});
}
const result: IStringDictionary<number> = {};
forEach(storedRecommendations, ({ key, value }) => {
if (typeof value === 'number') {
result[key.toLowerCase()] = value;
}
});
return result;
}
private storeCachedRecommendations(): void {
const storedRecommendations: IStringDictionary<number> = {};
forEach(this.fileBasedRecommendations, ({ key, value }) => storedRecommendations[key] = value.recommendedTime);
this.storageService.store(recommendationsStorageKey, JSON.stringify(storedRecommendations), StorageScope.GLOBAL);
}
}

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IProductService } from 'vs/platform/product/common/productService';
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
export class KeymapRecommendations extends ExtensionRecommendations {
private _recommendations: ExtensionRecommendation[] = [];
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
constructor(
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
@IProductService private readonly productService: IProductService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@INotificationService notificationService: INotificationService,
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService);
}
protected async doActivate(): Promise<void> {
if (this.productService.keymapExtensionTips) {
this._recommendations = this.productService.keymapExtensionTips.map(extensionId => (<ExtensionRecommendation>{
extensionId: extensionId.toLowerCase(),
source: 'application',
reason: {
reasonId: ExtensionRecommendationReason.Application,
reasonText: ''
}
}));
}
}
}

View File

@@ -4,14 +4,17 @@
*--------------------------------------------------------------------------------------------*/
.monaco-action-bar .action-item .action-label.extension-action {
padding: 0 5px;
outline-offset: 2px;
line-height: initial;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.monaco-action-bar .action-item .action-label.extension-action.label {
padding: 0 5px;
outline-offset: 2px;
}
.monaco-action-bar .action-item .action-label.extension-action.text,
.monaco-action-bar .action-item .action-label.extension-action.label {
line-height: 14px;
@@ -19,8 +22,9 @@
}
.monaco-action-bar .action-item .action-label.extension-action.icon {
padding: 0 2px;
height: 18px;
width: 10px;
width: 16px;
}
.monaco-action-bar .action-item .action-label.extension-action.multiserver.install:after,

View File

@@ -0,0 +1,258 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EXTENSION_IDENTIFIER_PATTERN, IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { IFileService } from 'vs/platform/files/common/files';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { distinct, flatten, coalesce } from 'vs/base/common/arrays';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IExtensionsConfigContent, ExtensionRecommendationSource, ExtensionRecommendationReason, IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { parse } from 'vs/base/common/json';
import { EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { InstallWorkspaceRecommendedExtensionsAction, ShowRecommendedExtensionsAction } 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';
type ExtensionWorkspaceRecommendationsNotificationClassification = {
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
const choiceNever = localize('neverShowAgain', "Don't Show Again");
const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore';
export class WorkspaceRecommendations extends ExtensionRecommendations {
private _recommendations: ExtensionRecommendation[] = [];
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
private _ignoredRecommendations: string[] = [];
get ignoredRecommendations(): ReadonlyArray<string> { return this._ignoredRecommendations; }
constructor(
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@ILogService private readonly logService: ILogService,
@IFileService private readonly fileService: IFileService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@INotificationService notificationService: INotificationService,
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService);
}
protected async doActivate(): Promise<void> {
await this.fetch();
this._register(this.contextService.onDidChangeWorkspaceFolders(e => this.onWorkspaceFoldersChanged(e)));
this.promptWorkspaceRecommendations();
}
/**
* Parse all extensions.json files, fetch workspace recommendations, filter out invalid and unwanted ones
*/
private async fetch(): Promise<void> {
const extensionsConfigBySource = await this.fetchExtensionsConfigBySource();
const { invalidRecommendations, message } = await this.validateExtensions(extensionsConfigBySource.map(({ contents }) => contents));
if (invalidRecommendations.length) {
this.notificationService.warn(`The below ${invalidRecommendations.length} extension(s) in workspace recommendations have issues:\n${message}`);
}
this._ignoredRecommendations = [];
for (const extensionsConfig of extensionsConfigBySource) {
for (const unwantedRecommendation of extensionsConfig.contents.unwantedRecommendations) {
if (invalidRecommendations.indexOf(unwantedRecommendation) === -1) {
this._ignoredRecommendations.push(unwantedRecommendation);
}
}
for (const extensionId of extensionsConfig.contents.recommendations) {
if (invalidRecommendations.indexOf(extensionId) === -1) {
this._recommendations.push({
extensionId,
source: extensionsConfig.source,
reason: {
reasonId: ExtensionRecommendationReason.Workspace,
reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.")
}
});
}
}
}
}
private async promptWorkspaceRecommendations(): Promise<void> {
const allowedRecommendations = this.recommendations.filter(rec => this.isExtensionAllowedToBeRecommended(rec.extensionId));
if (allowedRecommendations.length === 0 || this.hasToIgnoreWorkspaceRecommendationNotifications()) {
return;
}
let installed = await this.extensionManagementService.getInstalled(ExtensionType.User);
installed = installed.filter(l => this.extensionEnablementService.getEnablementState(l) !== EnablementState.DisabledByExtensionKind); // Filter extensions disabled by kind
const recommendations = allowedRecommendations.filter(({ extensionId }) => installed.every(local => !areSameExtensions({ id: extensionId }, local.identifier)));
if (!recommendations.length) {
return;
}
return new Promise<void>(c => {
this.notificationService.prompt(
Severity.Info,
localize('workspaceRecommended', "This workspace has extension recommendations."),
[{
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));
installAllAction.run();
installAllAction.dispose();
c(undefined);
}
}, {
label: localize('showRecommendations', "Show Recommendations"),
run: () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'show' });
const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations"));
showAction.run();
showAction.dispose();
c(undefined);
}
}, {
label: choiceNever,
isSecondary: true,
run: () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' });
this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE);
c(undefined);
}
}],
{
sticky: true,
onCancel: () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' });
c(undefined);
}
}
);
});
}
private async fetchExtensionsConfigBySource(): Promise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource }[]> {
const workspace = this.contextService.getWorkspace();
const result = await Promise.all([
this.resolveWorkspaceExtensionConfig(workspace),
...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder))
]);
return coalesce(result);
}
private async resolveWorkspaceExtensionConfig(workspace: IWorkspace): Promise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource } | null> {
try {
if (workspace.configuration) {
const content = await this.fileService.readFile(workspace.configuration);
const extensionsConfigContent = <IExtensionsConfigContent | undefined>parse(content.value.toString())['extensions'];
const contents = this.parseExtensionConfig(extensionsConfigContent);
if (contents) {
return { contents, source: workspace };
}
}
} catch (e) { /* Ignore */ }
return null;
}
private async resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): Promise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource } | null> {
try {
const content = await this.fileService.readFile(workspaceFolder.toResource(EXTENSIONS_CONFIG));
const extensionsConfigContent = <IExtensionsConfigContent>parse(content.value.toString());
const contents = this.parseExtensionConfig(extensionsConfigContent);
if (contents) {
return { contents, source: workspaceFolder };
}
} catch (e) { /* ignore */ }
return null;
}
private async validateExtensions(contents: IExtensionsConfigContent[]): Promise<{ validRecommendations: string[], invalidRecommendations: string[], message: string }> {
const validExtensions: string[] = [];
const invalidExtensions: string[] = [];
const extensionsToQuery: string[] = [];
let message = '';
const allRecommendations = distinct(flatten(contents.map(({ recommendations }) => recommendations || [])));
const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
for (const extensionId of allRecommendations) {
if (regEx.test(extensionId)) {
extensionsToQuery.push(extensionId);
} else {
invalidExtensions.push(extensionId);
message += `${extensionId} (bad format) Expected: <provider>.<name>\n`;
}
}
if (extensionsToQuery.length) {
try {
const queryResult = await this.galleryService.query({ names: extensionsToQuery, pageSize: extensionsToQuery.length }, CancellationToken.None);
const extensions = queryResult.firstPage.map(extension => extension.identifier.id.toLowerCase());
for (const extensionId of extensionsToQuery) {
if (extensions.indexOf(extensionId) === -1) {
invalidExtensions.push(extensionId);
message += `${extensionId} (not found in marketplace)\n`;
} else {
validExtensions.push(extensionId);
}
}
} catch (e) {
this.logService.warn('Error querying extensions gallery', e);
}
}
return { validRecommendations: validExtensions, invalidRecommendations: invalidExtensions, message };
}
private async onWorkspaceFoldersChanged(event: IWorkspaceFoldersChangeEvent): Promise<void> {
if (event.added.length) {
const oldWorkspaceRecommended = this._recommendations;
await this.fetch();
// Suggest only if at least one of the newly added recommendations was not suggested before
if (this._recommendations.some(current => oldWorkspaceRecommended.every(old => current.extensionId !== old.extensionId))) {
this.promptWorkspaceRecommendations();
}
}
}
private parseExtensionConfig(extensionsConfigContent: IExtensionsConfigContent | undefined): IExtensionsConfigContent | null {
if (extensionsConfigContent) {
return {
recommendations: distinct((extensionsConfigContent.recommendations || []).map(e => e.toLowerCase())),
unwantedRecommendations: distinct((extensionsConfigContent.unwantedRecommendations || []).map(e => e.toLowerCase()))
};
}
return null;
}
private hasToIgnoreWorkspaceRecommendationNotifications(): boolean {
return this.hasToIgnoreRecommendationNotifications() || this.storageService.getBoolean(ignoreWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE, false);
}
}

View File

@@ -82,6 +82,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio
if (visible) {
const indicator: IStatusbarEntry = {
text: '$(sync~spin) ' + nls.localize('profilingExtensionHost', "Profiling Extension Host"),
ariaLabel: nls.localize('profilingExtensionHost', "Profiling Extension Host"),
tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."),
command: 'workbench.action.extensionHostProfilder.stop'
};

View File

@@ -60,7 +60,7 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
// Global actions
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ShowRuntimeExtensionsAction, ShowRuntimeExtensionsAction.ID, ShowRuntimeExtensionsAction.LABEL), 'Show Running Extensions', localize('developer', "Developer"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ShowRuntimeExtensionsAction), 'Show Running Extensions', localize('developer', "Developer"));
class ExtensionsContributions implements IWorkbenchContribution {
@@ -68,7 +68,7 @@ class ExtensionsContributions implements IWorkbenchContribution {
@IWorkbenchEnvironmentService workbenchEnvironmentService: INativeWorkbenchEnvironmentService
) {
if (workbenchEnvironmentService.extensionsPath) {
const openExtensionsFolderActionDescriptor = SyncActionDescriptor.create(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL);
const openExtensionsFolderActionDescriptor = SyncActionDescriptor.from(OpenExtensionsFolderAction);
actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel);
}
}

View File

@@ -21,7 +21,7 @@ import { append, $, addClass, toggleClass, Dimension, clearNode } from 'vs/base/
import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { clipboard } from 'electron';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IElectronService } from 'vs/platform/electron/node/electron';
@@ -48,7 +48,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
import { editorBackground } from 'vs/platform/theme/common/colorRegistry';
import { domEvent } from 'vs/base/browser/event';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
export const IExtensionHostProfileService = createDecorator<IExtensionHostProfileService>('extensionHostProfileService');
export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey<string>('profileSessionState', 'none');
@@ -126,7 +126,8 @@ export class RuntimeExtensionsEditor extends BaseEditor {
@IStorageService storageService: IStorageService,
@ILabelService private readonly _labelService: ILabelService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@IOpenerService private readonly _openerService: IOpenerService
@IOpenerService private readonly _openerService: IOpenerService,
@IClipboardService private readonly _clipboardService: IClipboardService
) {
super(RuntimeExtensionsEditor.ID, telemetryService, themeService, storageService);
@@ -338,7 +339,7 @@ export class RuntimeExtensionsEditor extends BaseEditor {
data.actionbar.push(this._instantiationService.createInstance(SlowExtensionAction, element.description, element.unresponsiveProfile), { icon: true, label: true });
}
if (isNonEmptyArray(element.status.runtimeErrors)) {
data.actionbar.push(new ReportExtensionIssueAction(element, this._openerService), { icon: true, label: true });
data.actionbar.push(new ReportExtensionIssueAction(element, this._openerService, this._clipboardService), { icon: true, label: true });
}
let title: string;
@@ -450,7 +451,7 @@ export class RuntimeExtensionsEditor extends BaseEditor {
const actions: IAction[] = [];
actions.push(new ReportExtensionIssueAction(e.element, this._openerService));
actions.push(new ReportExtensionIssueAction(e.element, this._openerService, this._clipboardService));
actions.push(new Separator());
if (e.element.marketplaceInfo) {
@@ -509,25 +510,29 @@ export class ReportExtensionIssueAction extends Action {
private readonly _url: string;
constructor(extension: {
description: IExtensionDescription;
marketplaceInfo: IExtension;
status?: IExtensionsStatus;
unresponsiveProfile?: IExtensionHostProfile
}, @IOpenerService private readonly openerService: IOpenerService) {
constructor(
extension: {
description: IExtensionDescription;
marketplaceInfo: IExtension;
status?: IExtensionsStatus;
unresponsiveProfile?: IExtensionHostProfile
},
@IOpenerService private readonly openerService: IOpenerService,
@IClipboardService private readonly clipboardService: IClipboardService
) {
super(ReportExtensionIssueAction._id, ReportExtensionIssueAction._label, 'extension-action report-issue');
this.enabled = extension.marketplaceInfo
&& extension.marketplaceInfo.type === ExtensionType.User
&& !!extension.description.repository && !!extension.description.repository.url;
this._url = ReportExtensionIssueAction._generateNewIssueUrl(extension);
this._url = this._generateNewIssueUrl(extension);
}
async run(): Promise<void> {
this.openerService.open(URI.parse(this._url));
}
private static _generateNewIssueUrl(extension: {
private _generateNewIssueUrl(extension: {
description: IExtensionDescription;
marketplaceInfo: IExtension;
status?: IExtensionsStatus;
@@ -543,7 +548,7 @@ export class ReportExtensionIssueAction extends Action {
let reason = 'Bug';
let title = 'Extension issue';
let message = ':warning: We have written the needed data into your clipboard. Please paste! :warning:';
clipboard.writeText('```json \n' + JSON.stringify(extension.status, null, '\t') + '\n```');
this.clipboardService.writeText('```json \n' + JSON.stringify(extension.status, null, '\t') + '\n```');
const osVersion = `${os.type()} ${os.arch()} ${os.release()}`;
const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&';
@@ -692,7 +697,11 @@ export class SaveExtensionHostProfileAction extends Action {
}
}
class RuntimeExtensionsEditorAccessibilityProvider implements IAccessibilityProvider<IRuntimeExtension> {
class RuntimeExtensionsEditorAccessibilityProvider implements IListAccessibilityProvider<IRuntimeExtension> {
getWidgetAriaLabel(): string {
return nls.localize('runtimeExtensions', "Runtime Extensions");
}
getAriaLabel(element: IRuntimeExtension): string | null {
return element.description.name;
}

View File

@@ -21,8 +21,8 @@ import { Emitter } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TestLifecycleService, productService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestContextService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -48,7 +48,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { FileService } from 'vs/platform/files/common/fileService';
import { NullLogService } from 'vs/platform/log/common/log';
import { NullLogService, ILogService } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { IFileService } from 'vs/platform/files/common/files';
@@ -57,6 +57,7 @@ import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/exten
import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService';
import { NoOpWorkspaceTagsService } from 'vs/workbench/contrib/tags/browser/workspaceTagsService';
import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags';
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
const mockExtensionGallery: IGalleryExtension[] = [
aGalleryExtension('MockExtension1', {
@@ -207,23 +208,23 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
instantiationService.stub(ITelemetryService, NullTelemetryService);
instantiationService.stub(IURLService, URLService);
instantiationService.stub(IWorkspaceTagsService, new NoOpWorkspaceTagsService());
instantiationService.set(IProductService, {
...productService,
...{
extensionTips: {
'ms-dotnettools.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}',
'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs,**/.babelrc}',
'lukehoban.Go': '**/*.go'
instantiationService.stub(IStorageService, new TestStorageService());
instantiationService.stub(ILogService, new NullLogService());
instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService());
instantiationService.stub(IProductService, <Partial<IProductService>>{
extensionTips: {
'ms-dotnettools.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}',
'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs,**/.babelrc}',
'lukehoban.Go': '**/*.go'
},
extensionImportantTips: {
'ms-python.python': {
'name': 'Python',
'pattern': '{**/*.py}'
},
extensionImportantTips: {
'ms-python.python': {
'name': 'Python',
'pattern': '{**/*.py}'
},
'ms-vscode.PowerShell': {
'name': 'PowerShell',
'pattern': '{**/*.ps,**/*.ps1}'
}
'ms-vscode.PowerShell': {
'name': 'PowerShell',
'pattern': '{**/*.ps,**/*.ps1}'
}
}
});
@@ -259,7 +260,6 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
instantiationService.stub(INotificationService, new TestNotificationService2());
testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false, showRecommendationsOnlyOnDemand: false });
instantiationService.stub(IStorageService, <Partial<IStorageService>>{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c: boolean) => c, store: () => { } });
instantiationService.stub(IModelService, <IModelService>{
getModels(): any { return []; },
onModelAdded: onModelAddedEvent.event
@@ -380,23 +380,14 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
});
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set for current workspace', () => {
instantiationService.stub(IStorageService, <Partial<IStorageService>>{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c });
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);
});
test('ExtensionRecommendationsService: No Recommendations of globally ignored recommendations', () => {
const storageGetterStub = (a: string, _: StorageScope, c?: string) => {
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]';
const ignoredRecommendations = '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]'; // ignore a stored recommendation and a workspace recommendation.
if (a === 'extensionsAssistant/recommendations') { return storedRecommendations; }
if (a === 'extensionsAssistant/ignored_recommendations') { return ignoredRecommendations; }
return c;
};
instantiationService.stub(IStorageService, <Partial<IStorageService>>{
get: storageGetterStub,
getBoolean: (a: string, _: StorageScope, c?: boolean) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c
});
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]', StorageScope.GLOBAL);
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]', StorageScope.GLOBAL);
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
@@ -413,10 +404,8 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
test('ExtensionRecommendationsService: No Recommendations of workspace ignored recommendations', () => {
const ignoredRecommendations = ['ms-dotnettools.csharp', 'mockpublisher2.mockextension2']; // ignore a stored recommendation and a workspace recommendation.
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
instantiationService.stub(IStorageService, <Partial<IStorageService>>{
get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c,
getBoolean: (a: string, _: StorageScope, c?: boolean) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c
});
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, ignoredRecommendations).then(() => {
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
@@ -432,19 +421,12 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
test('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', () => {
const storageGetterStub = (a: string, _: StorageScope, c?: string) => {
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.
if (a === 'extensionsAssistant/recommendations') { return storedRecommendations; }
if (a === 'extensionsAssistant/ignored_recommendations') { return globallyIgnoredRecommendations; }
return c;
};
const workspaceIgnoredRecommendations = ['ms-dotnettools.csharp']; // ignore a stored recommendation and a workspace recommendation.
instantiationService.stub(IStorageService, <Partial<IStorageService>>{
get: storageGetterStub,
getBoolean: (a: string, _: StorageScope, c?: boolean) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c
});
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL);
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations).then(() => {
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
@@ -459,19 +441,11 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
});
test('ExtensionRecommendationsService: Able to dynamically ignore/unignore global recommendations', () => {
const storageGetterStub = (a: string, _: StorageScope, c?: string) => {
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.
if (a === 'extensionsAssistant/recommendations') { return storedRecommendations; }
if (a === 'extensionsAssistant/ignored_recommendations') { return globallyIgnoredRecommendations; }
return c;
};
instantiationService.stub(IStorageService, <Partial<IStorageService>>{
get: storageGetterStub,
store: () => { },
getBoolean: (a: string, _: StorageScope, c?: boolean) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c
});
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL);
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
@@ -502,16 +476,11 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
});
test('test global extensions are modified and recommendation change event is fired when an extension is ignored', async () => {
const storageSetterTarget = sinon.spy();
const changeHandlerTarget = sinon.spy();
const ignoredExtensionId = 'Some.Extension';
instantiationService.stub(IStorageService, <any>{ // {{SQL CARBON EDIT}} strict-null-checks?
get: (a: string, b: StorageScope, c?: boolean) => a === 'extensionsAssistant/ignored_recommendations' ? '["ms-vscode.vscode"]' : c,
getBoolean: (a: string, b: StorageScope, c: boolean) => c,
store: (...args: any[]) => {
storageSetterTarget(...args);
}
});
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.GLOBAL);
await setUpFolderWorkspace('myFolder', []);
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
@@ -520,13 +489,12 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
await testObject.loadWorkspaceConfigPromise;
assert.ok(changeHandlerTarget.calledOnce);
assert.ok(changeHandlerTarget.getCall(0).calledWithMatch({ extensionId: 'Some.Extension', isRecommended: false }));
assert.ok(storageSetterTarget.calledWithExactly('extensionsAssistant/ignored_recommendations', `["ms-vscode.vscode","${ignoredExtensionId.toLowerCase()}"]`, StorageScope.GLOBAL));
assert.ok(changeHandlerTarget.getCall(0).calledWithMatch({ extensionId: ignoredExtensionId.toLowerCase(), isRecommended: false }));
});
test('ExtensionRecommendationsService: Get file based recommendations from storage (old format)', () => {
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]';
instantiationService.stub(IStorageService, <Partial<IStorageService>>{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, getBoolean: (a: string, b: StorageScope, c: boolean) => c });
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
return setUpFolderWorkspace('myFolder', []).then(() => {
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
@@ -545,7 +513,7 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
const now = Date.now();
const tenDaysOld = 10 * milliSecondsInADay;
const storedRecommendations = `{"ms-dotnettools.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`;
instantiationService.stub(IStorageService, <Partial<IStorageService>>{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, getBoolean: (a: string, b: StorageScope, c: boolean) => c });
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
return setUpFolderWorkspace('myFolder', []).then(() => {
testObject = instantiationService.createInstance(ExtensionRecommendationsService);

View File

@@ -11,12 +11,11 @@ import * as ExtensionsActions from 'vs/workbench/contrib/extensions/browser/exte
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension,
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, InstallOperation
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, InstallOperation, IExtensionTipsService
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService';
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IURLService } from 'vs/platform/url/common/url';
@@ -47,6 +46,12 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { ProgressService } from 'vs/workbench/services/progress/browser/progressService';
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test';
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
suite('ExtensionsActions Test', () => {
@@ -71,6 +76,8 @@ suite('ExtensionsActions Test', () => {
instantiationService.stub(IWorkspaceContextService, new TestContextService());
instantiationService.stub(IConfigurationService, new TestConfigurationService());
instantiationService.stub(IProgressService, ProgressService);
instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService());
instantiationService.stub(IProductService, {});
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);
instantiationService.stub(ISharedProcessService, TestSharedProcessService);
@@ -94,7 +101,10 @@ suite('ExtensionsActions Test', () => {
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter<IFormatterChangeEvent>().event });
instantiationService.set(IExtensionRecommendationsService, instantiationService.createInstance(ExtensionRecommendationsService));
instantiationService.stub(ILifecycleService, new TestLifecycleService());
instantiationService.stub(IExperimentService, instantiationService.createInstance(TestExperimentService));
instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService));
instantiationService.stub(IExtensionRecommendationsService, {});
instantiationService.stub(IURLService, URLService);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []);

View File

@@ -17,7 +17,6 @@ import {
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionRecommendationsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService';
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IURLService } from 'vs/platform/url/common/url';
@@ -49,7 +48,6 @@ import { IMenuService } from 'vs/platform/actions/common/actions';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
suite('ExtensionsListView Tests', () => {
let instantiationService: TestInstantiationService;
@@ -69,6 +67,7 @@ suite('ExtensionsListView Tests', () => {
const workspaceRecommendationA = aGalleryExtension('workspace-recommendation-A');
const workspaceRecommendationB = aGalleryExtension('workspace-recommendation-B');
const configBasedRecommendationA = aGalleryExtension('configbased-recommendation-A');
const fileBasedRecommendationA = aGalleryExtension('filebased-recommendation-A');
const fileBasedRecommendationB = aGalleryExtension('filebased-recommendation-B');
const otherRecommendationA = aGalleryExtension('other-recommendation-A');
@@ -110,27 +109,40 @@ suite('ExtensionsListView Tests', () => {
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
instantiationService.stub(IExtensionRecommendationsService, ExtensionRecommendationsService);
instantiationService.stub(IURLService, URLService);
instantiationService.stubPromise(IExtensionRecommendationsService, 'getWorkspaceRecommendations', [
{ extensionId: workspaceRecommendationA.identifier.id },
{ extensionId: workspaceRecommendationB.identifier.id }]);
instantiationService.stub(IExtensionRecommendationsService, 'getFileBasedRecommendations', [
{ extensionId: fileBasedRecommendationA.identifier.id },
{ extensionId: fileBasedRecommendationB.identifier.id }]);
instantiationService.stubPromise(IExtensionRecommendationsService, 'getOtherRecommendations', [
{ extensionId: otherRecommendationA.identifier.id }
]);
const reasons: { [key: string]: any } = {};
reasons[workspaceRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace };
reasons[workspaceRecommendationB.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace };
reasons[fileBasedRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.File };
reasons[fileBasedRecommendationB.identifier.id] = { reasonId: ExtensionRecommendationReason.File };
reasons[otherRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Executable };
instantiationService.stub(IExtensionRecommendationsService, 'getAllRecommendationsWithReason', reasons);
reasons[configBasedRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.WorkspaceConfig };
instantiationService.stub(IExtensionRecommendationsService, <Partial<IExtensionRecommendationsService>>{
getWorkspaceRecommendations() {
return Promise.resolve([
{ extensionId: workspaceRecommendationA.identifier.id },
{ extensionId: workspaceRecommendationB.identifier.id }]);
},
getConfigBasedRecommendations() {
return Promise.resolve([
{ extensionId: configBasedRecommendationA.identifier.id }
]);
},
getFileBasedRecommendations() {
return [
{ extensionId: fileBasedRecommendationA.identifier.id },
{ extensionId: fileBasedRecommendationB.identifier.id }
];
},
getOtherRecommendations() {
return Promise.resolve([
{ extensionId: otherRecommendationA.identifier.id }
]);
},
getAllRecommendationsWithReason() {
return reasons;
}
});
instantiationService.stub(IURLService, URLService);
});
setup(async () => {
@@ -140,7 +152,7 @@ suite('ExtensionsListView Tests', () => {
instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', []);
instantiationService.stub(IViewDescriptorService, {
getViewLocation(): ViewContainerLocation {
getViewLocationById(): ViewContainerLocation {
return ViewContainerLocation.Sidebar;
}
});
@@ -336,6 +348,7 @@ suite('ExtensionsListView Tests', () => {
test('Test @recommended query', () => {
const allRecommendedExtensions = [
configBasedRecommendationA,
fileBasedRecommendationA,
fileBasedRecommendationB,
otherRecommendationA
@@ -360,6 +373,7 @@ suite('ExtensionsListView Tests', () => {
const allRecommendedExtensions = [
workspaceRecommendationA,
workspaceRecommendationB,
configBasedRecommendationA,
fileBasedRecommendationA,
fileBasedRecommendationB,
otherRecommendationA

View File

@@ -12,12 +12,11 @@ import { IExtensionsWorkbenchService, ExtensionState, AutoCheckUpdatesConfigurat
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension,
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, InstallOperation
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, InstallOperation, IExtensionTipsService
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService';
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IURLService } from 'vs/platform/url/common/url';
@@ -41,6 +40,13 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { IProductService } from 'vs/platform/product/common/productService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test';
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
suite('ExtensionsWorkbenchServiceTest', () => {
@@ -62,6 +68,8 @@ suite('ExtensionsWorkbenchServiceTest', () => {
instantiationService.stub(ITelemetryService, NullTelemetryService);
instantiationService.stub(ILogService, NullLogService);
instantiationService.stub(IProgressService, ProgressService);
instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService());
instantiationService.stub(IProductService, {});
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);
instantiationService.stub(IURLService, URLService);
@@ -91,7 +99,10 @@ suite('ExtensionsWorkbenchServiceTest', () => {
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
instantiationService.set(IExtensionRecommendationsService, instantiationService.createInstance(ExtensionRecommendationsService));
instantiationService.stub(ILifecycleService, new TestLifecycleService());
instantiationService.stub(IExperimentService, instantiationService.createInstance(TestExperimentService));
instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService));
instantiationService.stub(IExtensionRecommendationsService, {});
instantiationService.stub(INotificationService, { prompt: () => null! });
});