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

Implement UI - Enable/Disable extension #2882

上级 1c90acd7
......@@ -7,11 +7,11 @@
import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { StorageScope } from 'vs/platform/storage/common/storage';
export interface IExtensionDescription {
id: string;
name: string;
displayName: string;
version: string;
publisher: string;
isBuiltin: boolean;
......@@ -68,6 +68,17 @@ export const IExtensionsRuntimeService = createDecorator<IExtensionsRuntimeServi
export interface IExtensionsRuntimeService {
_serviceBrand: any;
// setEnablement(id: string, enable: boolean, displayName: string): TPromise<boolean>;
getDisabledExtensions(scope?: StorageScope): string[];
/**
* Enable or disable the given extension.
* Returns a promise that resolves to boolean value.
* if resolves to `true` then requires restart for the change to take effect.
*/
setEnablement(identifier: string, enable: boolean, displayName: string): TPromise<boolean>;
/**
* if `true` returns extensions disabled for workspace
* if `false` returns extensions disabled globally
* if `undefined` returns all disabled extensions
*/
getDisabledExtensions(workspace?: boolean): string[];
}
......@@ -19,7 +19,6 @@ export interface IExtensionsViewlet extends IViewlet {
export enum ExtensionState {
Installing,
Installed,
NeedsRestart,
Uninstalled,
Disabled
}
......@@ -71,6 +70,7 @@ export interface IExtensionsWorkbenchService {
install(vsix: string): TPromise<void>;
install(extension: IExtension, promptToInstallDependencies?: boolean): TPromise<void>;
uninstall(extension: IExtension): TPromise<void>;
setEnablement(extension: IExtension, enable: boolean): TPromise<void>;
loadDependencies(extension: IExtension): TPromise<IExtensionDependencies>;
open(extension: IExtension, sideByside?: boolean): TPromise<any>;
}
......
......@@ -109,7 +109,7 @@ export class UninstallAction extends Action {
return;
}
this.enabled = this.extension.state === ExtensionState.Installed || this.extension.state === ExtensionState.NeedsRestart || this.extension.state === ExtensionState.Disabled;
this.enabled = this.extension.state === ExtensionState.Installed || this.extension.state === ExtensionState.Disabled;
}
run(): TPromise<any> {
......@@ -232,7 +232,7 @@ export class UpdateAction extends Action {
const canInstall = this.extensionsWorkbenchService.canInstall(this.extension);
const isInstalled = this.extension.state === ExtensionState.Installed
|| this.extension.state === ExtensionState.NeedsRestart;
|| this.extension.state === ExtensionState.Disabled;
this.enabled = canInstall && isInstalled && this.extension.outdated;
this.class = this.enabled ? UpdateAction.EnabledClass : UpdateAction.DisabledClass;
......@@ -259,8 +259,7 @@ export class EnableAction extends Action {
set extension(extension: IExtension) { this._extension = extension; this.update(); }
constructor(
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IInstantiationService private instantiationService: IInstantiationService
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super('extensions.enable', localize('enableAction', "Enable"), EnableAction.DisabledClass, false);
......@@ -275,22 +274,47 @@ export class EnableAction extends Action {
return;
}
this.enabled = this.extension.state === ExtensionState.NeedsRestart;
this.enabled = this.extension.state === ExtensionState.Disabled;
this.class = this.enabled ? EnableAction.EnabledClass : EnableAction.DisabledClass;
}
run(): TPromise<any> {
if (!window.confirm(localize('restart', "In order to enable this extension, this window of VS Code needs to be restarted.\n\nDo you want to continue?"))) {
return TPromise.as(null);
return this.extensionsWorkbenchService.setEnablement(this.extension, true);
}
}
export class DisableAction extends Action {
private static EnabledClass = 'extension-action disable';
private static DisabledClass = `${DisableAction.EnabledClass} disabled`;
private disposables: IDisposable[] = [];
private _extension: IExtension;
get extension(): IExtension { return this._extension; }
set extension(extension: IExtension) { this._extension = extension; this.update(); }
constructor(
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
) {
super('extensions.disable', localize('disableAction', "Disable"), DisableAction.DisabledClass, false);
this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update()));
this.update();
}
private update(): void {
if (!this.extension) {
this.enabled = false;
this.class = DisableAction.DisabledClass;
return;
}
const action = this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('restartNow', "Restart Now"));
return action.run();
this.enabled = this.extension.state === ExtensionState.Installed;
this.class = this.enabled ? DisableAction.EnabledClass : DisableAction.DisabledClass;
}
dispose(): void {
super.dispose();
this.disposables = dispose(this.disposables);
run(): TPromise<any> {
return this.extensionsWorkbenchService.setEnablement(this.extension, false);
}
}
......@@ -316,7 +340,7 @@ export class UpdateAllAction extends Action {
return this.extensionsWorkbenchService.local.filter(
e => this.extensionsWorkbenchService.canInstall(e)
&& e.type === LocalExtensionType.User
&& (e.state === ExtensionState.Installed || e.state === ExtensionState.NeedsRestart)
&& (e.state === ExtensionState.Installed || e.state === ExtensionState.Disabled)
&& e.outdated
);
}
......
......@@ -15,7 +15,7 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
import { once } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IExtension } from './extensions';
import { CombinedInstallAction, UpdateAction, EnableAction, BuiltinStatusLabelAction } from './extensionsActions';
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, BuiltinStatusLabelAction } from './extensionsActions';
import { Label, RatingsWidget, InstallWidget, StatusWidget } from './extensionsWidgets';
import { EventType } from 'vs/base/common/events';
......@@ -75,9 +75,10 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
const installAction = this.instantiationService.createInstance(CombinedInstallAction);
const updateAction = this.instantiationService.createInstance(UpdateAction);
const restartAction = this.instantiationService.createInstance(EnableAction);
const disableAction = this.instantiationService.createInstance(DisableAction);
actionbar.push([restartAction, updateAction, installAction, builtinStatusAction], actionOptions);
const disposables = [versionWidget, installCountWidget, ratingsWidget, installAction, builtinStatusAction, updateAction, restartAction, actionbar];
actionbar.push([restartAction, updateAction, disableAction, installAction, builtinStatusAction], actionOptions);
const disposables = [versionWidget, installCountWidget, ratingsWidget, installAction, builtinStatusAction, updateAction, restartAction, disableAction, actionbar];
return {
element, icon, name, installCount, ratings, status, author, description, disposables,
......@@ -90,6 +91,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
installAction.extension = extension;
updateAction.extension = extension;
restartAction.extension = extension;
disableAction.extension = extension;
statusWidget.extension = extension;
}
};
......
......@@ -11,7 +11,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IExtension, IExtensionsWorkbenchService, ExtensionState } from './extensions';
import { append, $, addClass, toggleClass } from 'vs/base/browser/dom';
import { IExtensionsRuntimeService } from 'vs/platform/extensions/common/extensions';
import { StorageScope } from 'vs/platform/storage/common/storage';
export interface IOptions {
extension?: IExtension;
......@@ -69,7 +68,7 @@ export class StatusWidget implements IDisposable {
const state = this.extension.state;
const installed = state === ExtensionState.Installed;
const disabled = state === ExtensionState.Disabled;
const disabledInWorkspace = this.extensionsRuntimeService.getDisabledExtensions(StorageScope.WORKSPACE).indexOf(this.extension.identifier) !== -1;
const disabledInWorkspace = this.extensionsRuntimeService.getDisabledExtensions(true).indexOf(this.extension.identifier) !== -1;
toggleClass(status, 'disabled', disabled);
toggleClass(status, 'active', installed);
......
......@@ -25,7 +25,7 @@ import {
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionTelemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IMessageService } from 'vs/platform/message/common/message';
import { IMessageService, LaterAction } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
import * as semver from 'semver';
import * as path from 'path';
......@@ -474,6 +474,19 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return this.extensionService.installFromGallery(gallery, promptToInstallDependencies);
}
setEnablement(extension: IExtension, enable: boolean): TPromise<any> {
return this.extensionsRuntimeService.setEnablement(extension.identifier, enable, extension.displayName).then(restart => {
if (restart) {
const message = enable ? localize('postEnableMessage', "In order to enable '{0}' extension, this window of VS Code needs to be restarted.", extension.displayName)
: localize('postDisableMessage', "In order to disable '{0}' extension, this window of VS Code needs to be restarted.", extension.displayName);
return this.messageService.show(Severity.Info, {
message,
actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('restartNow', "Restart Now")), LaterAction]
});
}
});
}
uninstall(extension: IExtension): TPromise<void> {
if (!(extension instanceof Extension)) {
return;
......@@ -590,7 +603,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
if (local) {
if (local.needsRestart) {
return ExtensionState.NeedsRestart;
return ExtensionState.Disabled;
} else {
return disabledExtensions.indexOf(`${local.publisher}.${local.name}`) === -1 ? ExtensionState.Installed : ExtensionState.Disabled;
}
......
......@@ -83,6 +83,7 @@
.monaco-action-bar .action-item.disabled .action-label.extension-action.install:not(.installing),
.monaco-action-bar .action-item.disabled .action-label.extension-action.update,
.monaco-action-bar .action-item.disabled .action-label.extension-action.enable,
.monaco-action-bar .action-item.disabled .action-label.extension-action.disable,
.monaco-action-bar .action-item.disabled .action-label.extension-action.built-in-status.user {
display: none;
}
......
......@@ -3,11 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { distinct } from 'vs/base/common/arrays';
import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { IExtensionsRuntimeService } from 'vs/platform/extensions/common/extensions';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IChoiceService } from 'vs/platform/message/common/message';
import { IChoiceService, Severity } from 'vs/platform/message/common/message';
const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensions/disabled';
......@@ -28,18 +30,67 @@ export class ExtensionsRuntimeService implements IExtensionsRuntimeService {
this.workspace = contextService.getWorkspace();
}
public getDisabledExtensions(scope?: StorageScope): string[] {
public setEnablement(identifier: string, enable: boolean, displayName: string): TPromise<boolean> {
const disabled = this.getDisabledExtensionsFromStorage().indexOf(identifier) !== -1;
if (!enable === disabled) {
return TPromise.wrap(true);
}
if (!this.workspace) {
return this.setGlobalEnablement(identifier, enable, displayName);
}
if (enable) {
if (this.getDisabledExtensionsFromStorage(StorageScope.GLOBAL).indexOf(identifier) !== -1) {
return this.choiceService.choose(Severity.Info, localize('enableExtensionGlobally', "Would you like to enable '{0}' extension globally?", displayName),
[localize('yes', "Yes"), localize('no', "No")])
.then((option) => {
if (option === 0) {
return TPromise.join([this.enableExtension(identifier, StorageScope.GLOBAL), this.enableExtension(identifier, StorageScope.WORKSPACE)]).then(() => true);
}
return TPromise.wrap(false);
});
}
return this.choiceService.choose(Severity.Info, localize('enableExtensionForWorkspace', "Would you like to enable '{0}' extension for this workspace?", displayName),
[localize('yes', "Yes"), localize('no', "No")])
.then((option) => {
if (option === 0) {
return this.enableExtension(identifier, StorageScope.WORKSPACE).then(() => true);
}
return TPromise.wrap(false);
});
} else {
return this.choiceService.choose(Severity.Info, localize('disableExtension', "Would you like to disable '{0}' extension for this workspace or globally?", displayName),
[localize('workspace', "Workspace"), localize('globally', "Globally"), localize('cancel', "Cancel")])
.then((option) => {
switch (option) {
case 0:
return this.disableExtension(identifier, StorageScope.WORKSPACE);
case 1:
return this.disableExtension(identifier, StorageScope.GLOBAL);
default: return TPromise.wrap(false);
}
});
}
}
public getDisabledExtensions(workspace?: boolean): string[] {
if (!this.allDisabledExtensions) {
this.globalDisabledExtensions = this.getDisabledExtensionsFromStorage(StorageScope.GLOBAL);
this.workspaceDisabledExtensions = this.getDisabledExtensionsFromStorage(StorageScope.WORKSPACE);
this.allDisabledExtensions = distinct([...this.globalDisabledExtensions, ...this.workspaceDisabledExtensions]);
}
switch (scope) {
case StorageScope.GLOBAL: return this.globalDisabledExtensions;
case StorageScope.WORKSPACE: return this.workspaceDisabledExtensions;
if (workspace === void 0) {
return this.allDisabledExtensions;
}
return this.allDisabledExtensions;
if (workspace) {
return this.workspaceDisabledExtensions;
}
return this.globalDisabledExtensions;
}
private getDisabledExtensionsFromStorage(scope?: StorageScope): string[] {
......@@ -52,8 +103,56 @@ export class ExtensionsRuntimeService implements IExtensionsRuntimeService {
return [...globallyDisabled, ...workspaceDisabled];
}
private setGlobalEnablement(identifier: string, enable: boolean, displayName: string): TPromise<boolean> {
if (enable) {
return this.choiceService.choose(Severity.Info, localize('enableExtensionGloballyNoWorkspace', "Would you like to enable '{0}' extension globally?", displayName),
[localize('yes', "Yes"), localize('no', "No")])
.then((option) => {
if (option === 0) {
return this.enableExtension(identifier, StorageScope.GLOBAL).then(() => true);
}
return TPromise.wrap(false);
});
} else {
return this.choiceService.choose(Severity.Info, localize('disableExtensionGlobally', "Would you like to disable '{0}' extension globally?", displayName),
[localize('yes', "Yes"), localize('no', "No")])
.then((option) => {
if (option === 0) {
return this.disableExtension(identifier, StorageScope.GLOBAL).then(() => true);
}
return TPromise.wrap(false);
});
}
}
private disableExtension(identifier: string, scope: StorageScope): TPromise<boolean> {
let disabledExtensions = this._getDisabledExtensions(scope);
disabledExtensions.push(identifier);
this._setDisabledExtensions(disabledExtensions, scope);
return TPromise.wrap(true);
}
private enableExtension(identifier: string, scope: StorageScope): TPromise<boolean> {
let disabledExtensions = this._getDisabledExtensions(scope);
const index = disabledExtensions.indexOf(identifier);
if (index !== -1) {
disabledExtensions.splice(index, 1);
this._setDisabledExtensions(disabledExtensions, scope);
return TPromise.wrap(true);
}
return TPromise.wrap(false);
}
private _getDisabledExtensions(scope: StorageScope): string[] {
const value = this.storageService.get(DISABLED_EXTENSIONS_STORAGE_PATH, scope, '');
return value ? distinct(value.split(',')) : [];
}
private _setDisabledExtensions(disabledExtensions: string[], scope: StorageScope): void {
if (disabledExtensions.length) {
this.storageService.store(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions.join(','), scope);
} else {
this.storageService.remove(DISABLED_EXTENSIONS_STORAGE_PATH, scope);
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册