提交 a8f5bf89 编写于 作者: S Sandeep Somavarapu

Adopt recommendations

- Get recommendation source along with recommendations
- Add recommendation source to extensions
- Adopt recommendations for muliple management
上级 3de9efd4
......@@ -12,6 +12,7 @@ import { IPager } from 'vs/base/common/paging';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILocalization } from 'vs/platform/localizations/common/localizations';
import URI from 'vs/base/common/uri';
import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace';
export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$';
export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
......@@ -392,15 +393,27 @@ export type RecommendationChangeNotification = {
isRecommended: boolean
};
export type DynamicRecommendation = 'dynamic';
export type ExecutableRecommendation = 'executable';
export type CachedRecommendation = 'cached';
export type ApplicationRecommendation = 'application';
export type ExtensionRecommendationSource = IWorkspace | IWorkspaceFolder | URI | DynamicRecommendation | ExecutableRecommendation | CachedRecommendation | ApplicationRecommendation;
export interface IExtensionRecommendation {
extensionId: string;
sources: ExtensionRecommendationSource[];
}
export const IExtensionTipsService = createDecorator<IExtensionTipsService>('extensionTipsService');
export interface IExtensionTipsService {
_serviceBrand: any;
getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; };
getFileBasedRecommendations(): string[];
getOtherRecommendations(): TPromise<string[]>;
getWorkspaceRecommendations(): TPromise<string[]>;
getKeymapRecommendations(): string[];
getFileBasedRecommendations(): IExtensionRecommendation[];
getOtherRecommendations(): TPromise<IExtensionRecommendation[]>;
getWorkspaceRecommendations(): TPromise<IExtensionRecommendation[]>;
getKeymapRecommendations(): IExtensionRecommendation[];
getAllRecommendations(): TPromise<IExtensionRecommendation[]>;
getKeywordsForExtension(extension: string): string[];
getRecommendationsForExtension(extension: string): string[];
getAllIgnoredRecommendations(): IIgnoredRecommendations;
......
......@@ -8,7 +8,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { Event } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import { IPager } from 'vs/base/common/paging';
import { IQueryOptions, IExtensionManifest, LocalExtensionType, EnablementState, ILocalExtension, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IQueryOptions, IExtensionManifest, LocalExtensionType, EnablementState, ILocalExtension, IGalleryExtension, ExtensionRecommendationSource } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views';
import { Registry } from 'vs/platform/registry/common/platform';
......@@ -58,6 +58,7 @@ export interface IExtension {
locals?: ILocalExtension[];
gallery?: IGalleryExtension;
isMalicious: boolean;
recommendationSources: ExtensionRecommendationSource[];
}
export interface IExtensionDependencies {
......
......@@ -18,7 +18,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate';
import { LocalExtensionType, IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionManagementServer, IExtensionManagementServerService, IGalleryExtension, ILocalExtension, IExtensionsConfigContent } from 'vs/platform/extensionManagement/common/extensionManagement';
import { LocalExtensionType, IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionManagementServer, IExtensionManagementServerService, IExtensionRecommendation, ExtensionRecommendationSource, IExtensionGalleryService, IGalleryExtension, ILocalExtension, IExtensionsConfigContent } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ToggleViewletAction } from 'vs/workbench/browser/viewlet';
......@@ -73,6 +73,21 @@ const promptDownloadManually = (extension: IGalleryExtension, message: string, i
}]);
};
const getExtensionManagementServerForRecommendationSource = (source: ExtensionRecommendationSource, extensionManagementServerService: IExtensionManagementServerService, contextService: IWorkspaceContextService): IExtensionManagementServer => {
if (source instanceof URI) {
return extensionManagementServerService.getExtensionManagementServer(source);
}
if (source === contextService.getWorkspace()) {
return extensionManagementServerService.getDefaultExtensionManagementServer();
}
for (const workspaceFolder of contextService.getWorkspace().folders) {
if (source === workspaceFolder) {
return extensionManagementServerService.getExtensionManagementServer(workspaceFolder.uri);
}
}
return extensionManagementServerService.getDefaultExtensionManagementServer();
};
export interface IExtensionAction extends IAction {
extension: IExtension;
}
......@@ -446,7 +461,8 @@ export class MultiServerInstallAction extends Action {
constructor(
@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService,
@IInstantiationService private instantiationService: IInstantiationService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IWorkspaceContextService private contextService: IWorkspaceContextService
) {
super(MultiServerInstallAction.ID, MultiServerInstallAction.InstallLabel, MultiServerInstallAction.Class, false);
this._installActions = this.extensionManagementServerService.extensionManagementServers.map(server => this.instantiationService.createInstance(InstallGalleryExtensionAction, `extensions.install.${server.location.authority}`, localize('installInServer', "{0}", server.location.authority), server));
......@@ -464,7 +480,21 @@ export class MultiServerInstallAction extends Action {
return;
}
this.enabled = this.extensionsWorkbenchService.canInstall(this.extension) && this.extension.state === ExtensionState.Uninstalled;
let isExtensionNotInstalledInRecommendedServer: boolean = false;
this._installActions.forEach((installAction, index) => {
const server = this.extensionManagementServerService.extensionManagementServers[index];
installAction.extension = this.extension.gallery;
installAction.label = localize('installInServer', "{0}", server.location.authority);
installAction.enabled = !this.extension.locals.some(local => this.extensionManagementServerService.getExtensionManagementServer(local.location) === server);
if (this.extension.recommendationSources && this.extension.recommendationSources.length) {
if (this.extension.recommendationSources.some(recommendationSource => getExtensionManagementServerForRecommendationSource(recommendationSource, this.extensionManagementServerService, this.contextService) === server)) {
installAction.label = localize('installInRecommendedServer', "{0} (Recommended)", server.location.authority);
isExtensionNotInstalledInRecommendedServer = isExtensionNotInstalledInRecommendedServer || installAction.enabled;
}
}
});
this.enabled = this.extensionsWorkbenchService.canInstall(this.extension) && (isExtensionNotInstalledInRecommendedServer || this.extension.locals.length === 0);
if (this.extension.state === ExtensionState.Installing) {
this.label = MultiServerInstallAction.InstallingLabel;
......@@ -475,10 +505,6 @@ export class MultiServerInstallAction extends Action {
this.class = MultiServerInstallAction.Class;
this.tooltip = MultiServerInstallAction.InstallLabel;
}
for (const installAction of this._installActions) {
installAction.extension = this.extension ? this.extension.gallery : null;
}
}
public run(): TPromise<any> {
......@@ -522,14 +548,6 @@ export class MultiServerUpdateAction extends Action {
}
private update(): void {
if (this.extension && this.extension.type === LocalExtensionType.User) {
const canInstall = this.extensionsWorkbenchService.canInstall(this.extension);
const isInstalled = this.extension.state === ExtensionState.Installed;
this.enabled = canInstall && isInstalled;
} else {
this.enabled = false;
}
this._updateActions.forEach((updateAction, index) => {
updateAction.extension = null;
if (this.extension && this.extension.locals && this.extension.gallery) {
......@@ -538,6 +556,7 @@ export class MultiServerUpdateAction extends Action {
updateAction.extension = { local, gallery: this.extension.gallery };
}
});
this.enabled = this._updateActions.some(action => action.enabled);
}
public run(): TPromise<any> {
......@@ -1382,82 +1401,69 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action {
private disposables: IDisposable[] = [];
private _recommendations: IExtensionRecommendation[] = [];
get recommendations(): IExtensionRecommendation[] { return this._recommendations; }
set recommendations(recommendations: IExtensionRecommendation[]) { this._recommendations = recommendations; this.enabled = this._recommendations.length > 0; }
constructor(
id: string = InstallWorkspaceRecommendedExtensionsAction.ID,
label: string = InstallWorkspaceRecommendedExtensionsAction.LABEL,
recommendations: IExtensionRecommendation[],
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IViewletService private viewletService: IViewletService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionTipsService private extensionTipsService: IExtensionTipsService,
@INotificationService private notificationService: INotificationService,
@IInstantiationService private instantiationService: IInstantiationService,
@IOpenerService private openerService: IOpenerService
@IOpenerService private openerService: IOpenerService,
@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService,
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService
) {
super(id, label, 'extension-action');
this.extensionsWorkbenchService.onChange(() => this.update(), this, this.disposables);
this.contextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables);
}
private update(): void {
this.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY;
if (this.enabled) {
this.extensionTipsService.getWorkspaceRecommendations().then(names => {
const installed = this.extensionsWorkbenchService.local.map(x => x.id.toLowerCase());
this.enabled = names.some(x => installed.indexOf(x.toLowerCase()) === -1);
});
}
this.recommendations = recommendations;
}
run(): TPromise<any> {
return this.extensionTipsService.getWorkspaceRecommendations().then(names => {
const installed = this.extensionsWorkbenchService.local.map(x => x.id.toLowerCase());
const toInstall = names.filter(x => installed.indexOf(x.toLowerCase()) === -1);
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
if (!toInstall.length) {
this.enabled = false;
this.notificationService.info(localize('allExtensionsInstalled', "All extensions recommended for this workspace have already been installed"));
viewlet.focus();
return TPromise.as(null);
}
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search('@recommended ');
viewlet.focus();
if (this.recommendations.length === 0) {
this.notificationService.info(localize('extensionInstalled', "The recommended extension has already been installed"));
return TPromise.as(null);
}
viewlet.search('@recommended ');
viewlet.focus();
return this.extensionsWorkbenchService.queryGallery({ names: toInstall, source: 'install-all-workspace-recommendations' }).then(pager => {
let installPromises = [];
let model = new PagedModel(pager);
let extensionsWithDependencies = [];
for (let i = 0; i < pager.total; i++) {
installPromises.push(model.resolve(i).then(e => {
if (e.dependencies && e.dependencies.length > 0) {
extensionsWithDependencies.push(e);
return TPromise.as(null);
} else {
return this.install(e);
}
}));
}
return TPromise.join(installPromises).then(() => {
return TPromise.join(extensionsWithDependencies.map(e => this.install(e)));
});
});
const names = this.recommendations.map(({ extensionId }) => extensionId);
return this.extensionGalleryService.query({ names, source: 'install-all-workspace-recommendations' }).then(pager => {
let installPromises = [];
let model = new PagedModel(pager);
for (let i = 0; i < pager.total; i++) {
installPromises.push(model.resolve(i).then(e => {
return this.install(e);
}));
}
return TPromise.join(installPromises);
});
});
});
}
private install(extension: IExtension): TPromise<void> {
return this.extensionsWorkbenchService.install(extension).then(null, err => {
if (!extension.gallery) {
return this.notificationService.error(err);
private install(extension: IGalleryExtension): TPromise<void> {
const servers: IExtensionManagementServer[] = [];
const recommendation = this.recommendations.filter(r => areSameExtensions({ id: r.extensionId }, extension.identifier))[0];
if (recommendation) {
for (const source of recommendation.sources || []) {
const server = getExtensionManagementServerForRecommendationSource(source, this.extensionManagementServerService, this.contextService);
if (servers.indexOf(server) === -1) {
servers.push(server);
}
}
}
if (!servers.length) {
servers.push(this.extensionManagementServerService.getDefaultExtensionManagementServer());
}
return TPromise.join(servers.map(server => server.extensionManagementService.installFromGallery(extension).then(null, err => {
console.error(err);
promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), this.instantiationService, this.notificationService, this.openerService);
});
promptDownloadManually(extension, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), this.instantiationService, this.notificationService, this.openerService);
}))).then(() => null);
}
dispose(): void {
......@@ -1466,67 +1472,40 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action {
}
}
export class InstallRecommendedExtensionAction extends Action {
export class InstallRecommendedExtensionAction extends InstallGalleryExtensionAction {
static readonly ID = 'workbench.extensions.action.installRecommendedExtension';
static LABEL = localize('installRecommendedExtension', "Install Recommended Extension");
private extensionId: string;
private disposables: IDisposable[] = [];
constructor(
extensionId: string,
extensionId: string, server: IExtensionManagementServer,
@IViewletService private viewletService: IViewletService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@INotificationService private notificationService: INotificationService,
@IInstantiationService private instantiationService: IInstantiationService,
@IOpenerService private openerService: IOpenerService
@INotificationService notificationService: INotificationService,
@IInstantiationService instantiationService: IInstantiationService,
@IOpenerService openerService: IOpenerService,
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService
) {
super(InstallRecommendedExtensionAction.ID, InstallRecommendedExtensionAction.LABEL, null);
super(InstallRecommendedExtensionAction.ID, InstallRecommendedExtensionAction.LABEL, server, notificationService, instantiationService, openerService);
this.extensionId = extensionId;
this.extensionsWorkbenchService.onChange(() => this.update(), this, this.disposables);
}
private update(): void {
this.enabled = !this.extensionsWorkbenchService.local.some(x => x.id.toLowerCase() === this.extensionId.toLowerCase());
}
run(): TPromise<any> {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
if (this.extensionsWorkbenchService.local.some(x => x.id.toLowerCase() === this.extensionId.toLowerCase())) {
this.enabled = false;
this.notificationService.info(localize('extensionInstalled', "The recommended extension has already been installed"));
viewlet.focus();
return TPromise.as(null);
}
viewlet.search('@recommended ');
viewlet.focus();
return this.extensionsWorkbenchService.queryGallery({ names: [this.extensionId], source: 'install-recommendation' }).then(pager => {
return (pager && pager.firstPage && pager.firstPage.length) ? this.install(pager.firstPage[0]) : TPromise.as(null);
});
return this.extensionGalleryService.query({ names: [this.extensionId], source: 'install-recommendation', pageSize: 1 })
.then(pager => {
if (pager && pager.firstPage && pager.firstPage.length) {
this.extension = pager.firstPage[0];
}
return super.run();
});
});
}
private install(extension: IExtension): TPromise<void> {
return this.extensionsWorkbenchService.install(extension).then(null, err => {
if (!extension.gallery) {
return this.notificationService.error(err);
}
console.error(err);
promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), this.instantiationService, this.notificationService, this.openerService);
});
}
dispose(): void {
this.disposables = dispose(this.disposables);
super.dispose();
}
}
export class IgnoreExtensionRecommendationAction extends Action {
......
......@@ -16,7 +16,7 @@ import { IExtensionManagementService, ILocalExtension, IExtensionEnablementServi
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { areSameExtensions, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { Severity, INotificationService } from 'vs/platform/notification/common/notification';
......@@ -136,7 +136,7 @@ export function getInstalledExtensions(accessor: ServicesAccessor): TPromise<IEx
export function isKeymapExtension(tipsService: IExtensionTipsService, extension: IExtensionStatus): boolean {
const cats = extension.local.manifest.categories;
return cats && cats.indexOf('Keymaps') !== -1 || tipsService.getKeymapRecommendations().indexOf(stripVersion(extension.identifier.id)) !== -1;
return cats && cats.indexOf('Keymaps') !== -1 || tipsService.getKeymapRecommendations().some(({ extensionId }) => areSameExtensions({ id: extensionId }, { id: getGalleryExtensionIdFromLocal(extension.local) }));
}
function stripVersion(id: string): string {
......
......@@ -12,7 +12,7 @@ import { assign } from 'vs/base/common/objects';
import { chain } from 'vs/base/common/event';
import { isPromiseCanceledError, create as createError } from 'vs/base/common/errors';
import { PagedModel, IPagedModel, IPager } from 'vs/base/common/paging';
import { SortBy, SortOrder, IQueryOptions, LocalExtensionType, IExtensionTipsService, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { SortBy, SortOrder, IQueryOptions, LocalExtensionType, IExtensionTipsService, EnablementState, IExtensionRecommendation, IExtensionManagementServerService, ExtensionRecommendationSource, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
......@@ -36,6 +36,9 @@ import { WorkbenchPagedList } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { distinct } from 'vs/base/common/arrays';
import URI from 'vs/base/common/uri';
export class ExtensionsListView extends ViewletPanel {
......@@ -53,12 +56,14 @@ export class ExtensionsListView extends ViewletPanel {
@IInstantiationService protected instantiationService: IInstantiationService,
@IThemeService private themeService: IThemeService,
@IExtensionService private extensionService: IExtensionService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionsWorkbenchService protected extensionsWorkbenchService: IExtensionsWorkbenchService,
@IEditorService private editorService: IEditorService,
@IExtensionTipsService protected tipsService: IExtensionTipsService,
@IModeService private modeService: IModeService,
@ITelemetryService private telemetryService: ITelemetryService,
@IConfigurationService configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IExtensionManagementServerService protected extensionManagementServerService: IExtensionManagementServerService
) {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService);
}
......@@ -80,6 +85,7 @@ export class ExtensionsListView extends ViewletPanel {
this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], {
ariaLabel: localize('extensions', "Extensions")
}) as WorkbenchPagedList<IExtension>;
this.disposables.push(this.list);
chain(this.list.onOpen)
.map(e => e.elements[0])
......@@ -326,14 +332,13 @@ export class ExtensionsListView extends ViewletPanel {
return this.extensionsWorkbenchService.queryLocal()
.then(result => result.filter(e => e.type === LocalExtensionType.User))
.then(local => {
const installedExtensions = local.map(x => `${x.publisher}.${x.name}`);
let fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
const fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
const othersPromise = this.tipsService.getOtherRecommendations();
const workspacePromise = this.tipsService.getWorkspaceRecommendations();
return TPromise.join([othersPromise, workspacePromise])
.then(([others, workspaceRecommendations]) => {
const names = this.getTrimmedRecommendations(installedExtensions, value, fileBasedRecommendations, others, workspaceRecommendations);
const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, others, workspaceRecommendations);
const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason();
/* __GDPR__
"extensionAllRecommendations:open" : {
......@@ -369,18 +374,16 @@ export class ExtensionsListView extends ViewletPanel {
return this.extensionsWorkbenchService.queryLocal()
.then(result => result.filter(e => e.type === LocalExtensionType.User))
.then(local => {
const installedExtensions = local.map(x => `${x.publisher}.${x.name}`);
let fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
const othersPromise = this.tipsService.getOtherRecommendations();
const workspacePromise = this.tipsService.getWorkspaceRecommendations();
return TPromise.join([othersPromise, workspacePromise])
.then(([others, workspaceRecommendations]) => {
workspaceRecommendations = workspaceRecommendations.map(x => x.toLowerCase());
fileBasedRecommendations = fileBasedRecommendations.filter(x => workspaceRecommendations.indexOf(x.toLowerCase()) === -1);
others = others.filter(x => workspaceRecommendations.indexOf(x.toLowerCase()) === -1);
fileBasedRecommendations = fileBasedRecommendations.filter(x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId));
others = others.filter(x => x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId));
const names = this.getTrimmedRecommendations(installedExtensions, value, fileBasedRecommendations, others, []);
const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, others, []);
const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason();
/* __GDPR__
......@@ -413,39 +416,75 @@ export class ExtensionsListView extends ViewletPanel {
}
// Given all recommendations, trims and returns recommendations in the relevant order after filtering out installed extensions
private getTrimmedRecommendations(installedExtensions: string[], value: string, fileBasedRecommendations: string[], otherRecommendations: string[], workpsaceRecommendations: string[], ) {
private getTrimmedRecommendations(installedExtensions: IExtension[], value: string, fileBasedRecommendations: IExtensionRecommendation[], otherRecommendations: IExtensionRecommendation[], workpsaceRecommendations: IExtensionRecommendation[]): string[] {
const totalCount = 8;
workpsaceRecommendations = workpsaceRecommendations
.filter(name => {
return installedExtensions.indexOf(name) === -1
&& name.toLowerCase().indexOf(value) > -1;
.filter(recommendation => {
return !this.isRecommendationInstalled(recommendation, installedExtensions)
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
});
fileBasedRecommendations = fileBasedRecommendations.filter(x => {
return installedExtensions.indexOf(x) === -1
&& workpsaceRecommendations.indexOf(x) === -1
&& x.toLowerCase().indexOf(value) > -1;
fileBasedRecommendations = fileBasedRecommendations.filter(recommendation => {
return !this.isRecommendationInstalled(recommendation, installedExtensions)
&& workpsaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId)
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
});
otherRecommendations = otherRecommendations.filter(x => {
return installedExtensions.indexOf(x) === -1
&& fileBasedRecommendations.indexOf(x) === -1
&& workpsaceRecommendations.indexOf(x) === -1
&& x.toLowerCase().indexOf(value) > -1;
otherRecommendations = otherRecommendations.filter(recommendation => {
return !this.isRecommendationInstalled(recommendation, installedExtensions)
&& fileBasedRecommendations.every(fileBasedRecommendation => fileBasedRecommendation.extensionId !== recommendation.extensionId)
&& workpsaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId)
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
});
let otherCount = Math.min(2, otherRecommendations.length);
let fileBasedCount = Math.min(fileBasedRecommendations.length, totalCount - workpsaceRecommendations.length - otherCount);
let names = workpsaceRecommendations;
names.push(...fileBasedRecommendations.splice(0, fileBasedCount));
names.push(...otherRecommendations.splice(0, otherCount));
const otherCount = Math.min(2, otherRecommendations.length);
const fileBasedCount = Math.min(fileBasedRecommendations.length, totalCount - workpsaceRecommendations.length - otherCount);
const recommendations = workpsaceRecommendations;
recommendations.push(...fileBasedRecommendations.splice(0, fileBasedCount));
recommendations.push(...otherRecommendations.splice(0, otherCount));
return distinct(recommendations.map(({ extensionId }) => extensionId));
}
private isRecommendationInstalled(recommendation: IExtensionRecommendation, installed: IExtension[]): boolean {
const extension = installed.filter(i => areSameExtensions({ id: i.id }, { id: recommendation.extensionId }))[0];
if (extension && extension.locals) {
const servers: IExtensionManagementServer[] = [];
for (const local of extension.locals) {
const server = this.extensionManagementServerService.getExtensionManagementServer(local.location);
if (servers.indexOf(server) === -1) {
servers.push(server);
}
}
for (const server of servers) {
if (extension.recommendationSources && extension.recommendationSources.length) {
if (extension.recommendationSources.some(recommendationSource => this.getExtensionManagementServerForRecommendationSource(recommendationSource) === server)) {
return true;
}
}
}
}
return false;
}
return names;
private getExtensionManagementServerForRecommendationSource(source: ExtensionRecommendationSource): IExtensionManagementServer {
if (source instanceof URI) {
return this.extensionManagementServerService.getExtensionManagementServer(source);
}
if (source === this.contextService.getWorkspace()) {
return this.extensionManagementServerService.getDefaultExtensionManagementServer();
}
for (const workspaceFolder of this.contextService.getWorkspace().folders) {
if (source === workspaceFolder) {
return this.extensionManagementServerService.getExtensionManagementServer(workspaceFolder.uri);
}
}
return this.extensionManagementServerService.getDefaultExtensionManagementServer();
}
private getWorkspaceRecommendationsModel(query: Query, options: IQueryOptions): TPromise<IPagedModel<IExtension>> {
const value = query.value.replace(/@recommended:workspace/g, '').trim().toLowerCase();
return this.tipsService.getWorkspaceRecommendations()
.then(recommendations => {
const names = recommendations.filter(name => name.toLowerCase().indexOf(value) > -1);
const names = recommendations.map(({ extensionId }) => extensionId).filter(name => name.toLowerCase().indexOf(value) > -1);
/* __GDPR__
"extensionWorkspaceRecommendations:open" : {
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
......@@ -464,8 +503,8 @@ export class ExtensionsListView extends ViewletPanel {
private getKeymapRecommendationsModel(query: Query, options: IQueryOptions): TPromise<IPagedModel<IExtension>> {
const value = query.value.replace(/@recommended:keymaps/g, '').trim().toLowerCase();
const names = this.tipsService.getKeymapRecommendations()
.filter(name => name.toLowerCase().indexOf(value) > -1);
const names: string[] = this.tipsService.getKeymapRecommendations().map(({ extensionId }) => extensionId)
.filter(extensionId => extensionId.toLowerCase().indexOf(value) > -1);
if (!names.length) {
return TPromise.as(new PagedModel([]));
......@@ -484,18 +523,20 @@ export class ExtensionsListView extends ViewletPanel {
}
private setModel(model: IPagedModel<IExtension>) {
this.list.model = model;
this.list.scrollTop = 0;
const count = this.count();
toggleClass(this.extensionsList, 'hidden', count === 0);
toggleClass(this.messageBox, 'hidden', count > 0);
this.badge.setCount(count);
if (count === 0 && this.isVisible()) {
this.messageBox.textContent = localize('no extensions found', "No extensions found.");
} else {
this.messageBox.textContent = '';
if (this.list) {
this.list.model = model;
this.list.scrollTop = 0;
const count = this.count();
toggleClass(this.extensionsList, 'hidden', count === 0);
toggleClass(this.messageBox, 'hidden', count > 0);
this.badge.setCount(count);
if (count === 0 && this.isVisible()) {
this.messageBox.textContent = localize('no extensions found', "No extensions found.");
} else {
this.messageBox.textContent = '';
}
}
}
......@@ -533,8 +574,9 @@ export class ExtensionsListView extends ViewletPanel {
}
dispose(): void {
this.disposables = dispose(this.disposables);
super.dispose();
this.disposables = dispose(this.disposables);
this.list = null;
}
static isBuiltInExtensionsQuery(query: string): boolean {
......@@ -679,13 +721,14 @@ export class RecommendedExtensionsView extends ExtensionsListView {
export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
private installAllAction: InstallWorkspaceRecommendedExtensionsAction;
renderBody(container: HTMLElement): void {
super.renderBody(container);
this.disposables.push(this.tipsService.onRecommendationChange(() => {
this.show('');
}));
this.disposables.push(this.tipsService.onRecommendationChange(() => this.update()));
this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.setRecommendationsToInstall()));
this.disposables.push(this.contextService.onDidChangeWorkbenchState(() => this.update()));
}
renderHeader(container: HTMLElement): void {
......@@ -698,22 +741,37 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
animated: false
});
actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));
const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL);
this.installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, []);
const configureWorkspaceFolderAction = this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL);
installAllAction.class = 'octicon octicon-cloud-download';
this.installAllAction.class = 'octicon octicon-cloud-download';
configureWorkspaceFolderAction.class = 'octicon octicon-pencil';
actionbar.push([installAllAction], { icon: true, label: false });
actionbar.push([this.installAllAction], { icon: true, label: false });
actionbar.push([configureWorkspaceFolderAction], { icon: true, label: false });
this.disposables.push(actionbar);
this.disposables.push(...[this.installAllAction, configureWorkspaceFolderAction, actionbar]);
}
async show(query: string): TPromise<IPagedModel<IExtension>> {
async show(): TPromise<IPagedModel<IExtension>> {
let model = await super.show('@recommended:workspace');
this.setExpanded(model.length > 0);
return model;
}
private update(): void {
this.show();
this.setRecommendationsToInstall();
}
private setRecommendationsToInstall(): TPromise<void> {
return this.getRecommendationsToInstall()
.then(recommendations => { this.installAllAction.recommendations = recommendations; });
}
private getRecommendationsToInstall(): TPromise<IExtensionRecommendation[]> {
return this.tipsService.getWorkspaceRecommendations()
.then(recommendations => recommendations.filter(({ extensionId }) => this.extensionsWorkbenchService.local.some(i => areSameExtensions({ id: extensionId }, { id: i.id }))));
}
}
\ No newline at end of file
......@@ -18,7 +18,7 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest,
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState,
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, IExtensionTipsService, ExtensionRecommendationSource, IExtensionRecommendation
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
......@@ -48,6 +48,7 @@ class Extension implements IExtension {
public get local(): ILocalExtension { return this.locals[0]; }
public enablementState: EnablementState = EnablementState.Enabled;
public recommendationSources: ExtensionRecommendationSource[];
constructor(
private galleryService: IExtensionGalleryService,
......@@ -350,7 +351,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
@IWindowService private windowService: IWindowService,
@ILogService private logService: ILogService,
@IProgressService2 private progressService: IProgressService2,
@IExtensionService private runtimeExtensionService: IExtensionService
@IExtensionService private runtimeExtensionService: IExtensionService,
@IExtensionTipsService private extensionTipsService: IExtensionTipsService
) {
this.stateProvider = ext => this.getExtensionState(ext);
......@@ -385,8 +387,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
}
queryLocal(): TPromise<IExtension[]> {
return this.extensionService.getInstalled()
.then(installed => this.getDistinctInstalledExtensions(installed)
return TPromise.join([this.extensionService.getInstalled(), this.extensionTipsService.getAllRecommendations()])
.then(([installed, allRecommendations]) => this.getDistinctInstalledExtensions(installed)
.then(distinctInstalled => {
const installedById = index(this.installed, e => e.local.identifier.id);
const groupById = groupBy(installed, i => getGalleryExtensionIdFromLocal(i));
......@@ -397,6 +399,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
const extension = installedById[local.identifier.id] || new Extension(this.galleryService, this.stateProvider, locals, null, this.telemetryService);
extension.locals = locals;
extension.enablementState = this.extensionEnablementService.getEnablementState(local);
const recommendation = allRecommendations.filter(r => areSameExtensions({ id: r.extensionId }, { id: extension.id }))[0];
if (recommendation) {
extension.recommendationSources = recommendation.sources || [];
}
return extension;
});
......@@ -406,19 +412,20 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
}
queryGallery(options: IQueryOptions = {}): TPromise<IPager<IExtension>> {
return this.extensionService.getExtensionsReport().then(report => {
const maliciousSet = getMaliciousExtensionsSet(report);
return this.galleryService.query(options)
.then(result => mapPager(result, gallery => this.fromGallery(gallery, maliciousSet)))
.then(null, err => {
if (/No extension gallery service configured/.test(err.message)) {
return TPromise.as(singlePagePager([]));
}
return TPromise.wrapError<IPager<IExtension>>(err);
});
});
return TPromise.join([this.extensionTipsService.getAllRecommendations(), this.extensionService.getExtensionsReport()])
.then(([allRecommendations, report]) => {
const maliciousSet = getMaliciousExtensionsSet(report);
return this.galleryService.query(options)
.then(result => mapPager(result, gallery => this.fromGallery(gallery, maliciousSet, allRecommendations)))
.then(null, err => {
if (/No extension gallery service configured/.test(err.message)) {
return TPromise.as(singlePagePager([]));
}
return TPromise.wrapError<IPager<IExtension>>(err);
});
});
}
loadDependencies(extension: IExtension): TPromise<IExtensionDependencies> {
......@@ -426,20 +433,21 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
return TPromise.wrap<IExtensionDependencies>(null);
}
return this.extensionService.getExtensionsReport().then(report => {
const maliciousSet = getMaliciousExtensionsSet(report);
return TPromise.join([this.extensionTipsService.getAllRecommendations(), this.extensionService.getExtensionsReport()])
.then(([allRecommendations, report]) => {
const maliciousSet = getMaliciousExtensionsSet(report);
return this.galleryService.loadAllDependencies((<Extension>extension).dependencies.map(id => <IExtensionIdentifier>{ id }))
.then(galleryExtensions => galleryExtensions.map(galleryExtension => this.fromGallery(galleryExtension, maliciousSet)))
.then(extensions => [...this.local, ...extensions])
.then(extensions => {
const map = new Map<string, IExtension>();
for (const extension of extensions) {
map.set(extension.id, extension);
}
return new ExtensionDependencies(extension, extension.id, map);
});
});
return this.galleryService.loadAllDependencies((<Extension>extension).dependencies.map(id => <IExtensionIdentifier>{ id }))
.then(galleryExtensions => galleryExtensions.map(galleryExtension => this.fromGallery(galleryExtension, maliciousSet, allRecommendations)))
.then(extensions => [...this.local, ...extensions])
.then(extensions => {
const map = new Map<string, IExtension>();
for (const extension of extensions) {
map.set(extension.id, extension);
}
return new ExtensionDependencies(extension, extension.id, map);
});
});
}
open(extension: IExtension, sideByside: boolean = false): TPromise<any> {
......@@ -498,7 +506,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
return false;
}
private fromGallery(gallery: IGalleryExtension, maliciousExtensionSet: Set<string>): Extension {
private fromGallery(gallery: IGalleryExtension, maliciousExtensionSet: Set<string>, allRecommendations: IExtensionRecommendation[]): Extension {
let result = this.getInstalledExtensionMatchingGallery(gallery);
if (result) {
......@@ -518,6 +526,11 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
result.isMalicious = true;
}
const recommendation = allRecommendations.filter(r => areSameExtensions({ id: r.extensionId }, { id: result.id }))[0];
if (recommendation) {
result.recommendationSources = recommendation.sources || [];
}
return result;
}
......
......@@ -506,9 +506,9 @@ suite('ExtensionsTipsService Test', () => {
return testObject.loadRecommendationsPromise.then(() => {
const recommendations = testObject.getFileBasedRecommendations();
assert.equal(recommendations.length, 2);
assert.ok(recommendations.indexOf('ms-vscode.csharp') > -1); // stored recommendation that exists in product.extensionTips
assert.ok(recommendations.indexOf('ms-python.python') > -1); // stored recommendation that exists in product.extensionImportantTips
assert.ok(recommendations.indexOf('eg2.tslint') === -1); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips
assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-vscode.csharp')); // stored recommendation that exists in product.extensionTips
assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips
assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'eg2.tslint')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips
});
});
});
......@@ -525,10 +525,10 @@ suite('ExtensionsTipsService Test', () => {
return testObject.loadRecommendationsPromise.then(() => {
const recommendations = testObject.getFileBasedRecommendations();
assert.equal(recommendations.length, 2);
assert.ok(recommendations.indexOf('ms-vscode.csharp') > -1); // stored recommendation that exists in product.extensionTips
assert.ok(recommendations.indexOf('ms-python.python') > -1); // stored recommendation that exists in product.extensionImportantTips
assert.ok(recommendations.indexOf('eg2.tslint') === -1); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips
assert.ok(recommendations.indexOf('lukehoban.Go') === -1); //stored recommendation that is older than a week
assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-vscode.csharp')); // stored recommendation that exists in product.extensionTips
assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips
assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'eg2.tslint')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips
assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'lukehoban.Go')); //stored recommendation that is older than a week
});
});
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册