- Have only single installed extension

- Exclude not supported extensions
- Show remote badge
This commit is contained in:
Sandeep Somavarapu 2019-01-23 17:06:53 +01:00
parent 2a0a6ebad9
commit 9cc66db3c6
14 changed files with 358 additions and 549 deletions

View file

@ -1,135 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/extensionsWidgets';
import { Disposable } from 'vs/base/common/lifecycle';
import { IExtension, IExtensionsWorkbenchService, IExtensionContainer } from '../common/extensions';
import { append, $, addClass } from 'vs/base/browser/dom';
import * as platform from 'vs/base/common/platform';
import { localize } from 'vs/nls';
export abstract class ExtensionWidget extends Disposable implements IExtensionContainer {
private _extension: IExtension;
get extension(): IExtension { return this._extension; }
set extension(extension: IExtension) { this._extension = extension; this.update(); }
update(): void { this.render(); }
abstract render(): void;
}
export class Label extends ExtensionWidget {
constructor(
private element: HTMLElement,
private fn: (extension: IExtension) => string,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super();
this.render();
}
render(): void {
this.element.textContent = this.extension ? this.fn(this.extension) : '';
}
}
export class InstallCountWidget extends ExtensionWidget {
constructor(
private container: HTMLElement,
private small: boolean,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super();
addClass(container, 'extension-install-count');
this.render();
}
render(): void {
this.container.innerHTML = '';
if (!this.extension) {
return;
}
const installCount = this.extension.installCount;
if (installCount === undefined) {
return;
}
let installLabel: string;
if (this.small) {
if (installCount > 1000000) {
installLabel = `${Math.floor(installCount / 100000) / 10}M`;
} else if (installCount > 1000) {
installLabel = `${Math.floor(installCount / 1000)}K`;
} else {
installLabel = String(installCount);
}
}
else {
installLabel = installCount.toLocaleString(platform.locale);
}
append(this.container, $('span.octicon.octicon-cloud-download'));
const count = append(this.container, $('span.count'));
count.textContent = installLabel;
}
}
export class RatingsWidget extends ExtensionWidget {
constructor(
private container: HTMLElement,
private small: boolean,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super();
addClass(container, 'extension-ratings');
if (this.small) {
addClass(container, 'small');
}
this.render();
}
render(): void {
this.container.innerHTML = '';
if (!this.extension) {
return;
}
if (this.extension.rating === undefined) {
return;
}
if (this.small && !this.extension.ratingCount) {
return;
}
const rating = Math.round(this.extension.rating * 2) / 2;
if (this.small) {
append(this.container, $('span.full.star'));
const count = append(this.container, $('span.count'));
count.textContent = String(rating);
} else {
for (let i = 1; i <= 5; i++) {
if (rating >= i) {
append(this.container, $('span.full.star'));
} else if (rating >= i - 0.5) {
append(this.container, $('span.half.star'));
} else {
append(this.container, $('span.empty.star'));
}
}
}
this.container.title = this.extension.ratingCount > 1 ? localize('ratedByUsers', "Rated by {0} users", this.extension.ratingCount) : localize('ratedBySingleUser', "Rated by 1 user");
}
}

View file

@ -60,7 +60,6 @@ export interface IExtension {
getChangelog(token: CancellationToken): Promise<string>;
hasChangelog(): boolean;
local?: ILocalExtension;
locals?: ILocalExtension[];
gallery?: IGalleryExtension;
isMalicious: boolean;
}

View file

@ -25,10 +25,10 @@ import { IExtensionManifest, IKeyBinding, IView, IViewContainer, ExtensionType }
import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies, ExtensionContainers } from 'vs/workbench/parts/extensions/common/extensions';
import { RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/electron-browser/extensionsWidgets';
import { EditorOptions } from 'vs/workbench/common/editor';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, DisabledStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
@ -357,18 +357,17 @@ export class ExtensionEditor extends BaseEditor {
const actions = [
reloadAction,
this.instantiationService.createInstance(UpdateAction),
this.instantiationService.createInstance(EnableDropDownAction, runningExtensions),
this.instantiationService.createInstance(EnableDropDownAction),
this.instantiationService.createInstance(DisableDropDownAction, runningExtensions),
this.instantiationService.createInstance(CombinedInstallAction),
this.instantiationService.createInstance(MaliciousStatusLabelAction, true),
this.instantiationService.createInstance(DisabledStatusLabelAction, runningExtensions)
];
const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]);
extensionContainers.extension = extension;
this.extensionActionBar.clear();
this.extensionActionBar.push(actions, { icon: true, label: true });
this.transientDisposables.push(...[...actions, extensionContainers]);
this.transientDisposables.push(...[...actions, ...widgets, extensionContainers]);
this.setSubText(extension, reloadAction);
this.content.innerHTML = ''; // Clear content before setting navbar actions.

View file

@ -16,9 +16,9 @@ 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, IExtensionContainer } from 'vs/workbench/parts/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate';
import { IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionRecommendation, IGalleryExtension, IExtensionsConfigContent, IExtensionManagementServerService, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionRecommendation, IGalleryExtension, IExtensionsConfigContent, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { isUIExtension, ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@ -54,9 +54,6 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { clipboard } from 'electron';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { ILabelService } from 'vs/platform/label/common/label';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { coalesce } from 'vs/base/common/arrays';
const promptDownloadManually = (extension: IGalleryExtension | undefined, message: string, error: Error, instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService) => {
@ -515,7 +512,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
const groups: ExtensionAction[][] = [];
groups.push([
this.instantiationService.createInstance(EnableGloballyAction),
this.instantiationService.createInstance(CombinedEnableForWorkspaceAction, runningExtensions)
this.instantiationService.createInstance(EnableForWorkspaceAction)
]);
groups.push([
this.instantiationService.createInstance(DisableGloballyAction, runningExtensions),
@ -630,50 +627,6 @@ export class ExtensionInfoAction extends ExtensionAction {
}
}
export class CombinedEnableForWorkspaceAction extends ExtensionAction {
static readonly ID = 'extensions.enableForWorkspace';
static LABEL = localize('enableForWorkspaceAction', "Enable (Workspace)");
private enableForWorkspaceAction: EnableForWorkspaceAction;
private installInRemoteServerAction: InstallInRemoteServerAction;
constructor(readonly runningExtensions: IExtensionDescription[],
@IInstantiationService instantiationService: IInstantiationService
) {
super(CombinedEnableForWorkspaceAction.ID, CombinedEnableForWorkspaceAction.LABEL);
this.enableForWorkspaceAction = instantiationService.createInstance(EnableForWorkspaceAction);
this.installInRemoteServerAction = instantiationService.createInstance(InstallInRemoteServerAction, runningExtensions);
this.update();
}
update(): void {
this.enableForWorkspaceAction.extension = this.extension;
this.installInRemoteServerAction.extension = this.extension;
this.enableForWorkspaceAction.update();
this.installInRemoteServerAction.update();
this.enabled = this.installInRemoteServerAction.enabled || this.enableForWorkspaceAction.enabled;
}
run(): Promise<any> {
if (this.installInRemoteServerAction.enabled) {
return this.installInRemoteServerAction.run();
}
if (this.enableForWorkspaceAction.enabled) {
return this.enableForWorkspaceAction.run();
}
return Promise.resolve();
}
dispose(): void {
super.dispose();
this.enableForWorkspaceAction.dispose();
this.installInRemoteServerAction.dispose();
}
}
export class EnableForWorkspaceAction extends ExtensionAction {
static readonly ID = 'extensions.enableForWorkspace';
@ -699,65 +652,6 @@ export class EnableForWorkspaceAction extends ExtensionAction {
}
}
export class InstallInRemoteServerAction extends ExtensionAction {
static readonly ID = 'extensions.installInRemoteServerAction';
static LABEL = localize('enableForWorkspaceAction', "Enable (Workspace)");
constructor(readonly runningExtensions: IExtensionDescription[],
@IExtensionsWorkbenchService extensionWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IStorageService private readonly storageService: IStorageService,
@ILabelService private readonly labelService: ILabelService,
@IDialogService private readonly dialogService: IDialogService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super(InstallInRemoteServerAction.ID, InstallInRemoteServerAction.LABEL);
this.update();
}
update(): void {
this.enabled = false;
if (this.extensionManagementServerService.remoteExtensionManagementServer
&& this.extension && this.extension.locals && this.extension.locals.length > 0
&& !isUIExtension(this.extension.locals[0].manifest, this.configurationService)
&& this.extension.state === ExtensionState.Installed) {
const installedInRemoteServer = this.extension.locals.some(local => {
const server = this.extensionManagementServerService.getExtensionManagementServer(local.location);
return !!server && server.authority === this.extensionManagementServerService.remoteExtensionManagementServer!.authority;
});
if (!installedInRemoteServer) {
this.enabled = !this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier));
}
}
}
async run(): Promise<any> {
if (!this.enabled) {
return Promise.resolve();
}
if (this.storageService.getBoolean('askToInstallRemoteServerExtension', StorageScope.GLOBAL, true)) {
const message = localize('install extension', "Enabling the '{0}' extension will also install it in {1}. Would you like to continue?", this.extension.displayName, this.labelService.getHostLabel() || this.extensionManagementServerService.remoteExtensionManagementServer!.authority);
const response = await this.dialogService.confirm({ type: 'info', message, checkbox: { label: localize('do not ask me again', "Do not ask me again") } });
if (!response || !response.confirmed) {
return Promise.resolve();
}
if (response.checkboxChecked) {
this.storageService.store('askToInstallRemoteServerExtension', false, StorageScope.GLOBAL);
}
}
const galleryExtension = this.extension.gallery ? this.extension.gallery : await this.extensionGalleryService.getExtension(this.extension.local!.identifier);
if (galleryExtension) {
return this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.installFromGallery(galleryExtension);
} else {
const zipLocation = await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.zip(this.extension.local!);
return this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.unzip(zipLocation, this.extension.type!);
}
}
}
export class EnableGloballyAction extends ExtensionAction {
static readonly ID = 'extensions.enableGlobally';
@ -765,9 +659,7 @@ export class EnableGloballyAction extends ExtensionAction {
constructor(
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService
) {
super(EnableGloballyAction.ID, EnableGloballyAction.LABEL);
this.update();
@ -775,15 +667,7 @@ export class EnableGloballyAction extends ExtensionAction {
update(): void {
this.enabled = false;
if (this.extension && this.extension.locals && this.extension.local) {
if (!isUIExtension(this.extension.local.manifest, this.configurationService) && this.extensionManagementServerService.remoteExtensionManagementServer) {
if (!this.extension.locals.some(local => {
const server = this.extensionManagementServerService.getExtensionManagementServer(local.location);
return !!server && server.authority === this.extensionManagementServerService.remoteExtensionManagementServer!.authority;
})) {
return;
}
}
if (this.extension && this.extension.local) {
this.enabled = this.extension.state === ExtensionState.Installed && this.extension.enablementState === EnablementState.Disabled && this.extensionEnablementService.canChangeEnablement(this.extension.local);
}
}
@ -891,12 +775,11 @@ export abstract class ExtensionEditorDropDownAction extends ExtensionDropDownAct
export class EnableDropDownAction extends ExtensionEditorDropDownAction {
constructor(
runningExtensions: IExtensionDescription[],
@IInstantiationService instantiationService: IInstantiationService
) {
super('extensions.enable', localize('enableAction', "Enable"), [
instantiationService.createInstance(EnableGloballyAction),
instantiationService.createInstance(CombinedEnableForWorkspaceAction, runningExtensions)
instantiationService.createInstance(EnableForWorkspaceAction)
], instantiationService);
}
}
@ -1080,9 +963,7 @@ export class ReloadAction extends ExtensionAction {
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IWindowService private readonly windowService: IWindowService,
@IExtensionService private readonly extensionService: IExtensionService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService
) {
super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false);
this.throttler = new Throttler();
@ -1136,33 +1017,14 @@ export class ReloadAction extends ExtensionAction {
return;
}
} else {
const uiExtension = isUIExtension(installed.local.manifest, this.configurationService);
if (!isDisabled) {
let enableReload = true;
if (this.extensionManagementServerService.remoteExtensionManagementServer && installed.locals) {
if (uiExtension) {
// Only UI extension from local server requires reload if it is not running on the server
enableReload = installed.locals.some(local => {
const server = this.extensionManagementServerService.getExtensionManagementServer(local.location);
return !!server && server.authority === this.extensionManagementServerService.localExtensionManagementServer.authority;
});
} else {
enableReload = installed.locals.some(local => {
const server = this.extensionManagementServerService.getExtensionManagementServer(local.location);
return !!server && server.authority === this.extensionManagementServerService.remoteExtensionManagementServer!.authority;
});
}
}
if (enableReload === true) {
this.enabled = true;
if (!isEnabled) {
this.tooltip = localize('postInstallTooltip', "Please reload Visual Studio Code to complete the installation of this extension.");
} else {
this.tooltip = localize('postEnableTooltip', "Please reload Visual Studio Code to complete the enabling of this extension.");
}
return;
this.enabled = true;
if (!isEnabled) {
this.tooltip = localize('postInstallTooltip', "Please reload Visual Studio Code to complete the installation of this extension.");
} else {
this.tooltip = localize('postEnableTooltip', "Please reload Visual Studio Code to complete the enabling of this extension.");
}
return;
}
}
return;
@ -1661,44 +1523,6 @@ export class ChangeSortAction extends Action {
}
}
export class ChangeGroupAction extends Action {
private query: Query;
private disposables: IDisposable[] = [];
constructor(
id: string,
label: string,
onSearchChange: Event<string>,
private groupBy: string,
@IViewletService private readonly viewletService: IViewletService
) {
super(id, label, undefined, true);
if (groupBy === undefined) {
throw new Error('bad arguments');
}
this.query = Query.parse('');
onSearchChange(this.onSearchChange, this, this.disposables);
this.onSearchChange('');
}
private onSearchChange(value: string): void {
const query = Query.parse(value);
this.query = new Query(query.value, query.sortBy, this.groupBy || query.groupBy);
}
run(): Promise<void> {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search(this.query.toString());
viewlet.focus();
});
}
}
export class ConfigureRecommendedExtensionsCommandsContributor extends Disposable implements IWorkbenchContribution {
private workspaceContextKey = new RawContextKey<boolean>('workspaceRecommendations', true);
@ -2235,43 +2059,6 @@ export class MaliciousStatusLabelAction extends ExtensionAction {
}
}
export class DisabledStatusLabelAction extends ExtensionAction {
private static readonly Class = 'disable-status';
constructor(
private runningExtensions: IExtensionDescription[],
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ILabelService private readonly labelService: ILabelService
) {
super('extensions.install', localize('disabled', "Disabled"), `${DisabledStatusLabelAction.Class} hide`, false);
this.update();
}
update(): void {
this.class = `${DisabledStatusLabelAction.Class} hide`;
this.tooltip = '';
if (this.extension && this.extension.local && !this.extension.isMalicious && !this.runningExtensions.some(e => ExtensionIdentifier.equals(e.identifier, this.extension.identifier.id))) {
if (this.extensionManagementServerService.remoteExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.configurationService) && this.extension.locals) {
const installedInRemoteServer = this.extension.locals.some(local => {
const server = this.extensionManagementServerService.getExtensionManagementServer(local.location);
return !!server && server.authority === this.extensionManagementServerService.remoteExtensionManagementServer!.authority;
});
if (!installedInRemoteServer) {
this.class = `${DisabledStatusLabelAction.Class}`;
this.label = localize('disabled NonUI Extension', "Disabled for this Workspace because it is not installed in {0}.", this.labelService.getHostLabel() || this.extensionManagementServerService.remoteExtensionManagementServer.authority);
return;
}
}
}
}
run(): Promise<any> {
return Promise.resolve(null);
}
}
export class DisableAllAction extends Action {
static readonly ID = 'workbench.extensions.action.disableAll';

View file

@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { append, $, addClass, removeClass, toggleClass } from 'vs/base/browser/dom';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Action } from 'vs/base/common/actions';
@ -14,12 +13,11 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
import { Event } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IExtension, IExtensionsWorkbenchService, ExtensionContainers } from 'vs/workbench/parts/extensions/common/extensions';
import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, extensionButtonProminentBackground, extensionButtonProminentForeground, MaliciousStatusLabelAction, ExtensionActionItem } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionItem } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { Label, RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget } from 'vs/workbench/parts/extensions/electron-browser/extensionsWidgets';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionTipsService, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { INotificationService } from 'vs/platform/notification/common/notification';
export interface IExtensionsViewState {
@ -57,27 +55,17 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
@INotificationService private readonly notificationService: INotificationService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionService private readonly extensionService: IExtensionService,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
@IThemeService private readonly themeService: IThemeService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService
) { }
get templateId() { return 'extension'; }
renderTemplate(root: HTMLElement): ITemplateData {
const bookmark = append(root, $('span.bookmark'));
append(bookmark, $('span.octicon.octicon-star'));
const applyBookmarkStyle = (theme) => {
const bgColor = theme.getColor(extensionButtonProminentBackground);
const fgColor = theme.getColor(extensionButtonProminentForeground);
bookmark.style.borderTopColor = bgColor ? bgColor.toString() : 'transparent';
bookmark.style.color = fgColor ? fgColor.toString() : 'white';
};
applyBookmarkStyle(this.themeService.getTheme());
const bookmarkStyler = this.themeService.onThemeChange(applyBookmarkStyle.bind(this));
const recommendationWidget = this.instantiationService.createInstance(RecommendationWidget, root);
const element = append(root, $('.extension'));
const icon = append(element, $<HTMLImageElement>('img.icon'));
const iconContainer = append(element, $('.icon-container'));
const icon = append(iconContainer, $<HTMLImageElement>('img.icon'));
const badgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, iconContainer);
const details = append(element, $('.details'));
const headerContainer = append(details, $('.header-container'));
const header = append(headerContainer, $('.header'));
@ -100,6 +88,8 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));
const widgets = [
recommendationWidget,
badgeWidget,
this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version),
this.instantiationService.createInstance(InstallCountWidget, installCount, true),
this.instantiationService.createInstance(RatingsWidget, ratings, true)
@ -114,7 +104,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]);
actionbar.push(actions, actionOptions);
const disposables = [...actions, ...widgets, actionbar, bookmarkStyler, extensionContainers];
const disposables = [...actions, ...widgets, actionbar, extensionContainers];
return {
root, element, icon, name, installCount, ratings, author, description, disposables, actionbar,
@ -178,13 +168,6 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
data.icon.style.visibility = 'inherit';
}
this.updateRecommendationStatus(extension, data);
data.extensionDisposables.push(this.extensionTipsService.onRecommendationChange(change => {
if (areSameExtensions({ id: change.extensionId }, extension.identifier)) {
this.updateRecommendationStatus(extension, data);
}
}));
data.name.textContent = extension.displayName;
data.author.textContent = extension.publisherDisplayName;
data.description.textContent = extension.description;
@ -209,24 +192,6 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
}, this, data.extensionDisposables);
}
private updateRecommendationStatus(extension: IExtension, data: ITemplateData) {
const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
let ariaLabel = extension.displayName + '. ';
if (!extRecommendations[extension.identifier.id.toLowerCase()]) {
removeClass(data.root, 'recommended');
data.root.title = '';
} else {
addClass(data.root, 'recommended');
ariaLabel += extRecommendations[extension.identifier.id.toLowerCase()].reasonText + ' ';
data.root.title = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
}
ariaLabel += localize('viewExtensionDetailsAria', "Press enter for extension details.");
data.root.setAttribute('aria-label', ariaLabel);
}
disposeTemplate(data: ITemplateData): void {
data.disposables = dispose(data.disposables);
}

View file

@ -22,7 +22,7 @@ import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, ExtensionS
import {
ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction,
ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction,
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, ChangeGroupAction
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction
} from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { IExtensionManagementService, IExtensionManagementServerService, IExtensionManagementServer, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
@ -35,7 +35,7 @@ import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/servi
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ViewsRegistry, IViewDescriptor } from 'vs/workbench/common/views';
import { ViewContainerViewlet, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IStorageService } 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';
@ -47,9 +47,6 @@ import { IWindowService } from 'vs/platform/windows/common/windows';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views';
import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery';
import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/parts/codeEditor/electron-browser/suggestEnabledInput';
import { alert } from 'vs/base/browser/ui/aria/aria';
@ -279,7 +276,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
private extensionsBox: HTMLElement;
private primaryActions: IAction[];
private secondaryActions: IAction[];
private groupByServerAction: IAction;
private disposables: IDisposable[] = [];
constructor(
@ -297,8 +293,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService
@IExtensionService extensionService: IExtensionService
) {
super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
@ -395,12 +390,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
getSecondaryActions(): IAction[] {
if (!this.secondaryActions) {
if (!this.groupByServerAction) {
this.groupByServerAction = this.instantiationService.createInstance(ChangeGroupAction, 'extensions.group.servers', localize('group by servers', "Group By: Server"), this.onSearchChange, 'server');
this.disposables.push(this.onSearchChange(value => {
this.groupByServerAction.enabled = !value || ExtensionsListView.isInstalledExtensionsQuery(value) || ExtensionsListView.isBuiltInExtensionsQuery(value);
}));
}
this.secondaryActions = [
this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL),
@ -414,7 +403,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Sort By: Rating"), this.onSearchChange, 'rating'),
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Sort By: Name"), this.onSearchChange, 'name'),
new Separator(),
...(this.extensionManagementServerService.remoteExtensionManagementServer ? [this.groupByServerAction, new Separator()] : []),
this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL),
...(this.configurationService.getValue(AutoUpdateConfigurationKey) ? [this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)] : [this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)]),
this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL),
@ -489,21 +477,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
}
}
protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel {
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
const extensionManagementServer = viewDescriptor.id === `server.extensionsList.${this.extensionManagementServerService.localExtensionManagementServer.authority}` ? this.extensionManagementServerService.localExtensionManagementServer
: viewDescriptor.id === `server.extensionsList.${this.extensionManagementServerService.remoteExtensionManagementServer.authority}` ? this.extensionManagementServerService.remoteExtensionManagementServer : null;
if (extensionManagementServer) {
const servicesCollection: ServiceCollection = new ServiceCollection();
servicesCollection.set(IExtensionManagementService, extensionManagementServer.extensionManagementService);
servicesCollection.set(IExtensionsWorkbenchService, new SyncDescriptor(ExtensionsWorkbenchService));
const instantiationService = this.instantiationService.createChild(servicesCollection);
return instantiationService.createInstance(viewDescriptor.ctor, options, [extensionManagementServer]) as ViewletPanel;
}
}
return this.instantiationService.createInstance(viewDescriptor.ctor, options) as ViewletPanel;
}
private count(): number {
return this.panels.reduce((count, view) => (<ExtensionsListView>view).count() + count, 0);
}

View file

@ -0,0 +1,244 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/extensionsWidgets';
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { IExtension, IExtensionsWorkbenchService, IExtensionContainer } from '../common/extensions';
import { append, $, addClass } from 'vs/base/browser/dom';
import * as platform from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILabelService } from 'vs/platform/label/common/label';
import { extensionButtonProminentBackground, extensionButtonProminentForeground } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { STATUS_BAR_HOST_NAME_BACKGROUND } from 'vs/workbench/parts/remote/electron-browser/remote.contribution';
export abstract class ExtensionWidget extends Disposable implements IExtensionContainer {
private _extension: IExtension;
get extension(): IExtension { return this._extension; }
set extension(extension: IExtension) { this._extension = extension; this.update(); }
update(): void { this.render(); }
abstract render(): void;
}
export class Label extends ExtensionWidget {
constructor(
private element: HTMLElement,
private fn: (extension: IExtension) => string,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super();
this.render();
}
render(): void {
this.element.textContent = this.extension ? this.fn(this.extension) : '';
}
}
export class InstallCountWidget extends ExtensionWidget {
constructor(
private container: HTMLElement,
private small: boolean,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super();
addClass(container, 'extension-install-count');
this.render();
}
render(): void {
this.container.innerHTML = '';
if (!this.extension) {
return;
}
const installCount = this.extension.installCount;
if (installCount === undefined) {
return;
}
let installLabel: string;
if (this.small) {
if (installCount > 1000000) {
installLabel = `${Math.floor(installCount / 100000) / 10}M`;
} else if (installCount > 1000) {
installLabel = `${Math.floor(installCount / 1000)}K`;
} else {
installLabel = String(installCount);
}
}
else {
installLabel = installCount.toLocaleString(platform.locale);
}
append(this.container, $('span.octicon.octicon-cloud-download'));
const count = append(this.container, $('span.count'));
count.textContent = installLabel;
}
}
export class RatingsWidget extends ExtensionWidget {
constructor(
private container: HTMLElement,
private small: boolean
) {
super();
addClass(container, 'extension-ratings');
if (this.small) {
addClass(container, 'small');
}
this.render();
}
render(): void {
this.container.innerHTML = '';
if (!this.extension) {
return;
}
if (this.extension.rating === undefined) {
return;
}
if (this.small && !this.extension.ratingCount) {
return;
}
const rating = Math.round(this.extension.rating * 2) / 2;
if (this.small) {
append(this.container, $('span.full.star'));
const count = append(this.container, $('span.count'));
count.textContent = String(rating);
} else {
for (let i = 1; i <= 5; i++) {
if (rating >= i) {
append(this.container, $('span.full.star'));
} else if (rating >= i - 0.5) {
append(this.container, $('span.half.star'));
} else {
append(this.container, $('span.empty.star'));
}
}
}
this.container.title = this.extension.ratingCount > 1 ? localize('ratedByUsers', "Rated by {0} users", this.extension.ratingCount) : localize('ratedBySingleUser', "Rated by 1 user");
}
}
export class RecommendationWidget extends ExtensionWidget {
private element: HTMLElement;
private disposables: IDisposable[] = [];
constructor(
private parent: HTMLElement,
@IThemeService private readonly themeService: IThemeService,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService
) {
super();
this.render();
this._register(toDisposable(() => this.clear()));
}
private clear(): void {
this.parent.title = '';
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);
}
this.element = null;
this.disposables = dispose(this.disposables);
}
render(): void {
this.clear();
if (!this.extension) {
return;
}
const updateRecommendationMarker = () => {
this.clear();
const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
if (extRecommendations[this.extension.identifier.id.toLowerCase()]) {
this.element = append(this.parent, $('div.bookmark'));
const recommendation = append(this.element, $('.recommendation'));
append(recommendation, $('span.octicon.octicon-star'));
const applyBookmarkStyle = (theme) => {
const bgColor = theme.getColor(extensionButtonProminentBackground);
const fgColor = theme.getColor(extensionButtonProminentForeground);
recommendation.style.borderTopColor = bgColor ? bgColor.toString() : 'transparent';
recommendation.style.color = fgColor ? fgColor.toString() : 'white';
};
applyBookmarkStyle(this.themeService.getTheme());
this.themeService.onThemeChange(applyBookmarkStyle, this, this.disposables);
this.parent.title = extRecommendations[this.extension.identifier.id.toLowerCase()].reasonText;
this.parent.setAttribute('aria-label', localize('viewRecommendedExtensionDetailsAria', "{0}. {1} Press enter for extension details.", this.extension.displayName, extRecommendations[this.extension.identifier.id.toLowerCase()].reasonText));
}
};
updateRecommendationMarker();
this.extensionTipsService.onRecommendationChange(() => updateRecommendationMarker(), this, this.disposables);
}
}
export class RemoteBadgeWidget extends ExtensionWidget {
private element: HTMLElement | null;
private disposables: IDisposable[] = [];
constructor(
private parent: HTMLElement,
@ILabelService private readonly labelService: ILabelService,
@IThemeService private readonly themeService: IThemeService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
) {
super();
this.render();
this._register(toDisposable(() => this.clear()));
}
private clear(): void {
if (this.element) {
this.parent.removeChild(this.element);
}
this.element = null;
this.disposables = dispose(this.disposables);
}
render(): void {
this.clear();
if (!this.extension || !this.extension.local) {
return;
}
const server = this.extensionManagementServerService.getExtensionManagementServer(this.extension.local.location);
if (server === this.extensionManagementServerService.remoteExtensionManagementServer) {
this.element = append(this.parent, $('div.extension-remote-badge'));
append(this.element, $('span.octicon.octicon-file-symlink-directory'));
const applyBadgeStyle = (theme) => {
const bgColor = this.themeService.getTheme().getColor(STATUS_BAR_HOST_NAME_BACKGROUND);
this.element.style.backgroundColor = bgColor ? bgColor.toString() : '';
};
applyBadgeStyle(this.themeService.getTheme());
this.themeService.onThemeChange(applyBadgeStyle, this, this.disposables);
const updateTitle = () => this.element.title = this.labelService.getHostLabel();
this.labelService.onDidChangeFormatters(() => updateTitle(), this, this.disposables);
updateTitle();
}
}
}

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -57,19 +57,18 @@
}
.extensions-viewlet > .extensions .monaco-list-row > .bookmark {
display: none;
}
.extensions-viewlet > .extensions .monaco-list-row.recommended > .bookmark {
display: inline-block;
height: 20px;
width: 20px;
}
.extensions-viewlet > .extensions .monaco-list-row > .bookmark > .recommendation {
border-right: 20px solid transparent;
border-top: 20px solid;
box-sizing: border-box;
}
.extensions-viewlet > .extensions .monaco-list-row > .bookmark > .octicon {
.extensions-viewlet > .extensions .monaco-list-row > .bookmark > .recommendation > .octicon {
position: absolute;
top: 1px;
left: 1px;
@ -91,7 +90,11 @@
background: url('loading.svg') center center no-repeat;
}
.extensions-viewlet > .extensions .extension > .icon {
.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container {
position: relative;
}
.extensions-viewlet > .extensions .extension > .icon-container > .icon {
width: 42px;
height: 42px;
padding: 10px 14px 10px 0;
@ -99,6 +102,12 @@
object-fit: contain;
}
.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container > .extension-remote-badge {
position: absolute;
right: 5px;
bottom: 5px;
}
.extensions-viewlet.narrow > .extensions .extension > .icon {
display: none;
}
@ -204,8 +213,8 @@
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .bookmark,
.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .bookmark,
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .icon,
.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .icon,
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .icon-container > .icon,
.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .icon-container > .icon,
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .header-container,
.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .header-container,
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .description,

View file

@ -46,4 +46,12 @@
.extension-ratings.small > .count {
margin-left: 2px;
}
.extension-remote-badge {
width: 22px;
height: 22px;
line-height: 22px;
border-radius: 20px;
text-align: center;
}

View file

@ -30,14 +30,11 @@ import product from 'vs/platform/node/product';
import { ILogService } from 'vs/platform/log/common/log';
import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { groupBy } from 'vs/base/common/collections';
import { Schemas } from 'vs/base/common/network';
import * as resources from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IFileService } from 'vs/platform/files/common/files';
import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion, IExtension as IPlatformExtension } from 'vs/platform/extensions/common/extensions';
import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion, IExtension as IPlatformExtension, isUIExtension } from 'vs/platform/extensions/common/extensions';
interface IExtensionStateProvider<T> {
(extension: Extension): T;
@ -45,13 +42,12 @@ interface IExtensionStateProvider<T> {
class Extension implements IExtension {
public get local(): ILocalExtension { return this.locals[0]; }
public enablementState: EnablementState = EnablementState.Enabled;
constructor(
private galleryService: IExtensionGalleryService,
private stateProvider: IExtensionStateProvider<ExtensionState>,
public locals: ILocalExtension[],
public local: ILocalExtension | undefined,
public gallery: IGalleryExtension | undefined,
private telemetryService: ITelemetryService,
private logService: ILogService,
@ -383,7 +379,6 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
@IWindowService private readonly windowService: IWindowService,
@ILogService private readonly logService: ILogService,
@IProgressService2 private readonly progressService: IProgressService2,
@IExtensionService private readonly runtimeExtensionService: IExtensionService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IStorageService private readonly storageService: IStorageService,
@IFileService private readonly fileService: IFileService
@ -430,23 +425,20 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
queryLocal(): Promise<IExtension[]> {
return this.extensionService.getInstalled()
.then(installed => this.getDistinctInstalledExtensions(installed)
.then(distinctInstalled => {
const installedById = index(this.installed, e => e.identifier.id);
const groupById = groupBy(installed, i => i.identifier.id);
this.installed = distinctInstalled.map(local => {
const locals = groupById[local.identifier.id];
locals.splice(locals.indexOf(local), 1);
locals.splice(0, 0, local);
const extension = installedById[local.identifier.id] || new Extension(this.galleryService, this.stateProvider, locals, undefined, this.telemetryService, this.logService, this.fileService);
extension.locals = locals;
extension.enablementState = this.extensionEnablementService.getEnablementState(local);
return extension;
});
.then(installed => {
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
installed = installed.filter(installed => this.belongsToWindow(installed));
}
const installedById = index(this.installed, e => e.identifier.id);
this.installed = installed.map(local => {
const extension = installedById[local.identifier.id] || new Extension(this.galleryService, this.stateProvider, local, undefined, this.telemetryService, this.logService, this.fileService);
extension.enablementState = this.extensionEnablementService.getEnablementState(local);
return extension;
});
this._onChange.fire(undefined);
return this.local;
}));
this._onChange.fire(undefined);
return this.local;
});
}
queryGallery(options: IQueryOptions = {}): Promise<IPager<IExtension>> {
@ -491,52 +483,19 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), undefined, sideByside ? SIDE_GROUP : ACTIVE_GROUP));
}
private getDistinctInstalledExtensions(allInstalled: ILocalExtension[]): Promise<ILocalExtension[]> {
if (!this.hasDuplicates(allInstalled)) {
return Promise.resolve(allInstalled);
private belongsToWindow(extension: ILocalExtension): boolean {
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
return true;
}
return Promise.all([this.runtimeExtensionService.getExtensions(), this.extensionEnablementService.getDisabledExtensions()])
.then(([runtimeExtensions, disabledExtensionIdentifiers]) => {
const groups = groupBy(allInstalled, (extension: ILocalExtension) => {
const isDisabled = disabledExtensionIdentifiers.some(identifier => areSameExtensions(identifier, extension.identifier));
if (isDisabled) {
return extension.location.scheme === Schemas.file ? 'disabled:primary' : 'disabled:secondary';
} else {
return 'enabled';
}
});
const enabled: ILocalExtension[] = [];
const notRunningExtensions: ILocalExtension[] = [];
const seenExtensions: { [id: string]: boolean } = Object.create({});
for (const extension of (groups['enabled'] || [])) {
if (runtimeExtensions.some(r => r.extensionLocation.toString() === extension.location.toString())) {
enabled.push(extension);
seenExtensions[extension.identifier.id] = true;
} else {
notRunningExtensions.push(extension);
}
}
for (const extension of notRunningExtensions) {
if (!seenExtensions[extension.identifier.id]) {
enabled.push(extension);
seenExtensions[extension.identifier.id] = true;
}
}
const primaryDisabled = groups['disabled:primary'] || [];
const secondaryDisabled = (groups['disabled:secondary'] || []).filter(disabled => {
return primaryDisabled.every(p => !areSameExtensions(p.identifier, disabled.identifier));
});
return [...enabled, ...primaryDisabled, ...secondaryDisabled];
});
}
private hasDuplicates(extensions: ILocalExtension[]): boolean {
const seen: { [key: string]: boolean; } = Object.create(null);
for (const i of extensions) {
if (seen[i.identifier.id]) {
const extensionManagementServer = this.extensionManagementServerService.getExtensionManagementServer(extension.location);
if (isUIExtension(extension.manifest, this.configurationService)) {
if (this.extensionManagementServerService.localExtensionManagementServer === extensionManagementServer) {
return true;
}
} else {
if (this.extensionManagementServerService.remoteExtensionManagementServer === extensionManagementServer) {
return true;
}
seen[i.identifier.id] = true;
}
return false;
}
@ -554,7 +513,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
this.syncLocalWithGalleryExtension(result, gallery);
}
} else {
result = new Extension(this.galleryService, this.stateProvider, [], gallery, this.telemetryService, this.logService, this.fileService);
result = new Extension(this.galleryService, this.stateProvider, undefined, gallery, this.telemetryService, this.logService, this.fileService);
}
if (maliciousExtensionSet.has(result.identifier.id)) {
@ -581,13 +540,15 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
private syncLocalWithGalleryExtension(extension: Extension, gallery: IGalleryExtension) {
// Sync the local extension with gallery extension if local extension doesnot has metadata
Promise.all(extension.locals.map(local => local.metadata ? Promise.resolve(local) : this.extensionService.updateMetadata(local, { id: gallery.identifier.uuid, publisherDisplayName: gallery.publisherDisplayName, publisherId: gallery.publisherId })))
.then(locals => {
extension.locals = locals;
extension.gallery = gallery;
this._onChange.fire(extension);
this.eventuallyAutoUpdateExtensions();
});
if (extension.local) {
(extension.local.metadata ? Promise.resolve(extension.local) : this.extensionService.updateMetadata(extension.local, { id: gallery.identifier.uuid, publisherDisplayName: gallery.publisherDisplayName, publisherId: gallery.publisherId }))
.then(local => {
extension.local = local;
extension.gallery = gallery;
this._onChange.fire(extension);
this.eventuallyAutoUpdateExtensions();
});
}
}
checkForUpdates(): Promise<void> {
@ -699,9 +660,9 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
}
const ext = extension as Extension;
const toUninstall: ILocalExtension[] = ext.locals.length ? ext.locals : this.installed.filter(e => areSameExtensions(e.identifier, extension.identifier))[0].locals;
const toUninstall: ILocalExtension = ext.local ? ext.local : this.installed.filter(e => areSameExtensions(e.identifier, extension.identifier))[0].local;
if (!toUninstall || !toUninstall.length) {
if (!toUninstall) {
return Promise.reject(new Error('Missing local'));
}
@ -709,8 +670,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
return this.progressService.withProgress({
location: ProgressLocation.Extensions,
title: nls.localize('uninstallingExtension', 'Uninstalling extension....'),
source: `${toUninstall[0].identifier.id}`
}, () => Promise.all(toUninstall.map(local => this.extensionService.uninstall(local))).then(() => undefined));
source: `${toUninstall.identifier.id}`
}, () => this.extensionService.uninstall(toUninstall).then(() => undefined));
}
installVersion(extension: IExtension, version: string): Promise<void> {
@ -744,16 +705,16 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
}
const ext = extension as Extension;
const toReinstall: ILocalExtension[] = ext.locals.length ? ext.locals : this.installed.filter(e => areSameExtensions(e.identifier, extension.identifier))[0].locals;
const toReinstall: ILocalExtension = ext.local ? ext.local : this.installed.filter(e => areSameExtensions(e.identifier, extension.identifier))[0].local;
if (!toReinstall || !toReinstall.length) {
if (!toReinstall) {
return Promise.reject(new Error('Missing local'));
}
return this.progressService.withProgress({
location: ProgressLocation.Extensions,
source: `${toReinstall[0].identifier.id}`
}, () => Promise.all(toReinstall.map(local => this.extensionService.reinstallFromGallery(local))).then(() => undefined));
source: `${toReinstall.identifier.id}`
}, () => this.extensionService.reinstallFromGallery(toReinstall).then(() => undefined));
}
private installWithProgress(installTask: () => Promise<void>, extensionName?: string): Promise<void> {
@ -913,7 +874,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
let extension = this.installed.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0];
if (!extension) {
extension = new Extension(this.galleryService, this.stateProvider, [], gallery, this.telemetryService, this.logService, this.fileService);
extension = new Extension(this.galleryService, this.stateProvider, undefined, gallery, this.telemetryService, this.logService, this.fileService);
}
this.installing.push(extension);
@ -924,29 +885,22 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
private onDidInstallExtension(event: DidInstallExtensionEvent): void {
const { local, zipPath, error, gallery } = event;
const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] : null;
let extension: Extension | undefined = installingExtension ? installingExtension : zipPath ? new Extension(this.galleryService, this.stateProvider, local ? [local] : [], undefined, this.telemetryService, this.logService, this.fileService) : undefined;
this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing;
if (local && !this.belongsToWindow(local)) {
return;
}
let extension: Extension | undefined = installingExtension ? installingExtension : zipPath ? new Extension(this.galleryService, this.stateProvider, local, undefined, this.telemetryService, this.logService, this.fileService) : undefined;
if (extension) {
this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing;
if (local) {
const installed = this.installed.filter(e => areSameExtensions(e.identifier, extension!.identifier))[0];
if (installed) {
extension = installed;
const newServer = this.extensionManagementServerService.getExtensionManagementServer(local.location);
const existingLocal = newServer && installed.locals.filter(l => {
const server = this.extensionManagementServerService.getExtensionManagementServer(l.location);
return server && server.authority === newServer.authority;
})[0];
if (existingLocal) {
const locals = [...installed.locals];
locals.splice(installed.locals.indexOf(existingLocal), 1, local);
installed.locals = locals;
} else {
installed.locals = [...installed.locals, local];
}
} else {
extension.locals = [local];
this.installed.push(extension);
}
extension.local = local;
}
}
this._onChange.fire(error ? undefined : extension);

View file

@ -12,7 +12,7 @@ import { IExtensionsWorkbenchService, ExtensionState, AutoCheckUpdatesConfigurat
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
import {
IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, IGalleryExtension,
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, EnablementState, InstallOperation
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, EnablementState, InstallOperation, IExtensionManagementServerService, IExtensionManagementServer
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
@ -37,6 +37,9 @@ import { URLService } from 'vs/platform/url/common/urlService';
import { URI } from 'vs/base/common/uri';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/node/remoteAgentService';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
suite('ExtensionsWorkbenchServiceTest', () => {
@ -73,6 +76,9 @@ suite('ExtensionsWorkbenchServiceTest', () => {
}
});
instantiationService.stub(IRemoteAgentService, RemoteAgentService);
instantiationService.stub(IExtensionManagementServerService, instantiationService.createInstance(ExtensionManagementServerService, <IExtensionManagementServer>{ authority: 'vscode-local', extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local' }));
instantiationService.stub(IExtensionManagementService, ExtensionManagementService);
instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event);
instantiationService.stub(IExtensionManagementService, 'onDidInstallExtension', didInstallEvent.event);