Skip to content

Commit a24f7dd

Browse files
committed
Implement UI - Enable/Disable extension #2882
1 parent 1c90acd commit a24f7dd

File tree

8 files changed

+179
-30
lines changed

8 files changed

+179
-30
lines changed

src/vs/platform/extensions/common/extensions.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
import Severity from 'vs/base/common/severity';
88
import { TPromise } from 'vs/base/common/winjs.base';
99
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
10-
import { StorageScope } from 'vs/platform/storage/common/storage';
1110

1211
export interface IExtensionDescription {
1312
id: string;
1413
name: string;
14+
displayName: string;
1515
version: string;
1616
publisher: string;
1717
isBuiltin: boolean;
@@ -68,6 +68,17 @@ export const IExtensionsRuntimeService = createDecorator<IExtensionsRuntimeServi
6868

6969
export interface IExtensionsRuntimeService {
7070
_serviceBrand: any;
71-
// setEnablement(id: string, enable: boolean, displayName: string): TPromise<boolean>;
72-
getDisabledExtensions(scope?: StorageScope): string[];
71+
72+
/**
73+
* Enable or disable the given extension.
74+
* Returns a promise that resolves to boolean value.
75+
* if resolves to `true` then requires restart for the change to take effect.
76+
*/
77+
setEnablement(identifier: string, enable: boolean, displayName: string): TPromise<boolean>;
78+
/**
79+
* if `true` returns extensions disabled for workspace
80+
* if `false` returns extensions disabled globally
81+
* if `undefined` returns all disabled extensions
82+
*/
83+
getDisabledExtensions(workspace?: boolean): string[];
7384
}

src/vs/workbench/parts/extensions/electron-browser/extensions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export interface IExtensionsViewlet extends IViewlet {
1919
export enum ExtensionState {
2020
Installing,
2121
Installed,
22-
NeedsRestart,
2322
Uninstalled,
2423
Disabled
2524
}
@@ -71,6 +70,7 @@ export interface IExtensionsWorkbenchService {
7170
install(vsix: string): TPromise<void>;
7271
install(extension: IExtension, promptToInstallDependencies?: boolean): TPromise<void>;
7372
uninstall(extension: IExtension): TPromise<void>;
73+
setEnablement(extension: IExtension, enable: boolean): TPromise<void>;
7474
loadDependencies(extension: IExtension): TPromise<IExtensionDependencies>;
7575
open(extension: IExtension, sideByside?: boolean): TPromise<any>;
7676
}

src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts

+37-13
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export class UninstallAction extends Action {
109109
return;
110110
}
111111

112-
this.enabled = this.extension.state === ExtensionState.Installed || this.extension.state === ExtensionState.NeedsRestart || this.extension.state === ExtensionState.Disabled;
112+
this.enabled = this.extension.state === ExtensionState.Installed || this.extension.state === ExtensionState.Disabled;
113113
}
114114

115115
run(): TPromise<any> {
@@ -232,7 +232,7 @@ export class UpdateAction extends Action {
232232

233233
const canInstall = this.extensionsWorkbenchService.canInstall(this.extension);
234234
const isInstalled = this.extension.state === ExtensionState.Installed
235-
|| this.extension.state === ExtensionState.NeedsRestart;
235+
|| this.extension.state === ExtensionState.Disabled;
236236

237237
this.enabled = canInstall && isInstalled && this.extension.outdated;
238238
this.class = this.enabled ? UpdateAction.EnabledClass : UpdateAction.DisabledClass;
@@ -259,8 +259,7 @@ export class EnableAction extends Action {
259259
set extension(extension: IExtension) { this._extension = extension; this.update(); }
260260

261261
constructor(
262-
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
263-
@IInstantiationService private instantiationService: IInstantiationService
262+
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
264263
) {
265264
super('extensions.enable', localize('enableAction', "Enable"), EnableAction.DisabledClass, false);
266265

@@ -275,22 +274,47 @@ export class EnableAction extends Action {
275274
return;
276275
}
277276

278-
this.enabled = this.extension.state === ExtensionState.NeedsRestart;
277+
this.enabled = this.extension.state === ExtensionState.Disabled;
279278
this.class = this.enabled ? EnableAction.EnabledClass : EnableAction.DisabledClass;
280279
}
281280

282281
run(): TPromise<any> {
283-
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?"))) {
284-
return TPromise.as(null);
282+
return this.extensionsWorkbenchService.setEnablement(this.extension, true);
283+
}
284+
}
285+
286+
export class DisableAction extends Action {
287+
288+
private static EnabledClass = 'extension-action disable';
289+
private static DisabledClass = `${DisableAction.EnabledClass} disabled`;
290+
291+
private disposables: IDisposable[] = [];
292+
private _extension: IExtension;
293+
get extension(): IExtension { return this._extension; }
294+
set extension(extension: IExtension) { this._extension = extension; this.update(); }
295+
296+
constructor(
297+
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
298+
) {
299+
super('extensions.disable', localize('disableAction', "Disable"), DisableAction.DisabledClass, false);
300+
301+
this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update()));
302+
this.update();
303+
}
304+
305+
private update(): void {
306+
if (!this.extension) {
307+
this.enabled = false;
308+
this.class = DisableAction.DisabledClass;
309+
return;
285310
}
286311

287-
const action = this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('restartNow', "Restart Now"));
288-
return action.run();
312+
this.enabled = this.extension.state === ExtensionState.Installed;
313+
this.class = this.enabled ? DisableAction.EnabledClass : DisableAction.DisabledClass;
289314
}
290315

291-
dispose(): void {
292-
super.dispose();
293-
this.disposables = dispose(this.disposables);
316+
run(): TPromise<any> {
317+
return this.extensionsWorkbenchService.setEnablement(this.extension, false);
294318
}
295319
}
296320

@@ -316,7 +340,7 @@ export class UpdateAllAction extends Action {
316340
return this.extensionsWorkbenchService.local.filter(
317341
e => this.extensionsWorkbenchService.canInstall(e)
318342
&& e.type === LocalExtensionType.User
319-
&& (e.state === ExtensionState.Installed || e.state === ExtensionState.NeedsRestart)
343+
&& (e.state === ExtensionState.Installed || e.state === ExtensionState.Disabled)
320344
&& e.outdated
321345
);
322346
}

src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
1515
import { once } from 'vs/base/common/event';
1616
import { domEvent } from 'vs/base/browser/event';
1717
import { IExtension } from './extensions';
18-
import { CombinedInstallAction, UpdateAction, EnableAction, BuiltinStatusLabelAction } from './extensionsActions';
18+
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, BuiltinStatusLabelAction } from './extensionsActions';
1919
import { Label, RatingsWidget, InstallWidget, StatusWidget } from './extensionsWidgets';
2020
import { EventType } from 'vs/base/common/events';
2121

@@ -75,9 +75,10 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
7575
const installAction = this.instantiationService.createInstance(CombinedInstallAction);
7676
const updateAction = this.instantiationService.createInstance(UpdateAction);
7777
const restartAction = this.instantiationService.createInstance(EnableAction);
78+
const disableAction = this.instantiationService.createInstance(DisableAction);
7879

79-
actionbar.push([restartAction, updateAction, installAction, builtinStatusAction], actionOptions);
80-
const disposables = [versionWidget, installCountWidget, ratingsWidget, installAction, builtinStatusAction, updateAction, restartAction, actionbar];
80+
actionbar.push([restartAction, updateAction, disableAction, installAction, builtinStatusAction], actionOptions);
81+
const disposables = [versionWidget, installCountWidget, ratingsWidget, installAction, builtinStatusAction, updateAction, restartAction, disableAction, actionbar];
8182

8283
return {
8384
element, icon, name, installCount, ratings, status, author, description, disposables,
@@ -90,6 +91,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
9091
installAction.extension = extension;
9192
updateAction.extension = extension;
9293
restartAction.extension = extension;
94+
disableAction.extension = extension;
9395
statusWidget.extension = extension;
9496
}
9597
};

src/vs/workbench/parts/extensions/electron-browser/extensionsWidgets.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
1111
import { IExtension, IExtensionsWorkbenchService, ExtensionState } from './extensions';
1212
import { append, $, addClass, toggleClass } from 'vs/base/browser/dom';
1313
import { IExtensionsRuntimeService } from 'vs/platform/extensions/common/extensions';
14-
import { StorageScope } from 'vs/platform/storage/common/storage';
1514

1615
export interface IOptions {
1716
extension?: IExtension;
@@ -69,7 +68,7 @@ export class StatusWidget implements IDisposable {
6968
const state = this.extension.state;
7069
const installed = state === ExtensionState.Installed;
7170
const disabled = state === ExtensionState.Disabled;
72-
const disabledInWorkspace = this.extensionsRuntimeService.getDisabledExtensions(StorageScope.WORKSPACE).indexOf(this.extension.identifier) !== -1;
71+
const disabledInWorkspace = this.extensionsRuntimeService.getDisabledExtensions(true).indexOf(this.extension.identifier) !== -1;
7372
toggleClass(status, 'disabled', disabled);
7473
toggleClass(status, 'active', installed);
7574

src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchService.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionTelemetry';
2626
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
2727
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
28-
import { IMessageService } from 'vs/platform/message/common/message';
28+
import { IMessageService, LaterAction } from 'vs/platform/message/common/message';
2929
import Severity from 'vs/base/common/severity';
3030
import * as semver from 'semver';
3131
import * as path from 'path';
@@ -474,6 +474,19 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
474474
return this.extensionService.installFromGallery(gallery, promptToInstallDependencies);
475475
}
476476

477+
setEnablement(extension: IExtension, enable: boolean): TPromise<any> {
478+
return this.extensionsRuntimeService.setEnablement(extension.identifier, enable, extension.displayName).then(restart => {
479+
if (restart) {
480+
const message = enable ? localize('postEnableMessage', "In order to enable '{0}' extension, this window of VS Code needs to be restarted.", extension.displayName)
481+
: localize('postDisableMessage', "In order to disable '{0}' extension, this window of VS Code needs to be restarted.", extension.displayName);
482+
return this.messageService.show(Severity.Info, {
483+
message,
484+
actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('restartNow', "Restart Now")), LaterAction]
485+
});
486+
}
487+
});
488+
}
489+
477490
uninstall(extension: IExtension): TPromise<void> {
478491
if (!(extension instanceof Extension)) {
479492
return;
@@ -590,7 +603,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
590603

591604
if (local) {
592605
if (local.needsRestart) {
593-
return ExtensionState.NeedsRestart;
606+
return ExtensionState.Disabled;
594607
} else {
595608
return disabledExtensions.indexOf(`${local.publisher}.${local.name}`) === -1 ? ExtensionState.Installed : ExtensionState.Disabled;
596609
}

src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
.monaco-action-bar .action-item.disabled .action-label.extension-action.install:not(.installing),
8484
.monaco-action-bar .action-item.disabled .action-label.extension-action.update,
8585
.monaco-action-bar .action-item.disabled .action-label.extension-action.enable,
86+
.monaco-action-bar .action-item.disabled .action-label.extension-action.disable,
8687
.monaco-action-bar .action-item.disabled .action-label.extension-action.built-in-status.user {
8788
display: none;
8889
}

src/vs/workbench/services/extensions/electron-browser/extensions.ts

+105-6
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { localize } from 'vs/nls';
7+
import { TPromise } from 'vs/base/common/winjs.base';
68
import { distinct } from 'vs/base/common/arrays';
79
import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace';
810
import { IExtensionsRuntimeService } from 'vs/platform/extensions/common/extensions';
911
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
10-
import { IChoiceService } from 'vs/platform/message/common/message';
12+
import { IChoiceService, Severity } from 'vs/platform/message/common/message';
1113

1214
const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensions/disabled';
1315

@@ -28,18 +30,67 @@ export class ExtensionsRuntimeService implements IExtensionsRuntimeService {
2830
this.workspace = contextService.getWorkspace();
2931
}
3032

31-
public getDisabledExtensions(scope?: StorageScope): string[] {
33+
public setEnablement(identifier: string, enable: boolean, displayName: string): TPromise<boolean> {
34+
const disabled = this.getDisabledExtensionsFromStorage().indexOf(identifier) !== -1;
35+
36+
if (!enable === disabled) {
37+
return TPromise.wrap(true);
38+
}
39+
40+
if (!this.workspace) {
41+
return this.setGlobalEnablement(identifier, enable, displayName);
42+
}
43+
44+
if (enable) {
45+
if (this.getDisabledExtensionsFromStorage(StorageScope.GLOBAL).indexOf(identifier) !== -1) {
46+
return this.choiceService.choose(Severity.Info, localize('enableExtensionGlobally', "Would you like to enable '{0}' extension globally?", displayName),
47+
[localize('yes', "Yes"), localize('no', "No")])
48+
.then((option) => {
49+
if (option === 0) {
50+
return TPromise.join([this.enableExtension(identifier, StorageScope.GLOBAL), this.enableExtension(identifier, StorageScope.WORKSPACE)]).then(() => true);
51+
}
52+
return TPromise.wrap(false);
53+
});
54+
}
55+
return this.choiceService.choose(Severity.Info, localize('enableExtensionForWorkspace', "Would you like to enable '{0}' extension for this workspace?", displayName),
56+
[localize('yes', "Yes"), localize('no', "No")])
57+
.then((option) => {
58+
if (option === 0) {
59+
return this.enableExtension(identifier, StorageScope.WORKSPACE).then(() => true);
60+
}
61+
return TPromise.wrap(false);
62+
});
63+
} else {
64+
return this.choiceService.choose(Severity.Info, localize('disableExtension', "Would you like to disable '{0}' extension for this workspace or globally?", displayName),
65+
[localize('workspace', "Workspace"), localize('globally', "Globally"), localize('cancel', "Cancel")])
66+
.then((option) => {
67+
switch (option) {
68+
case 0:
69+
return this.disableExtension(identifier, StorageScope.WORKSPACE);
70+
case 1:
71+
return this.disableExtension(identifier, StorageScope.GLOBAL);
72+
default: return TPromise.wrap(false);
73+
}
74+
});
75+
}
76+
}
77+
78+
public getDisabledExtensions(workspace?: boolean): string[] {
3279
if (!this.allDisabledExtensions) {
3380
this.globalDisabledExtensions = this.getDisabledExtensionsFromStorage(StorageScope.GLOBAL);
3481
this.workspaceDisabledExtensions = this.getDisabledExtensionsFromStorage(StorageScope.WORKSPACE);
3582
this.allDisabledExtensions = distinct([...this.globalDisabledExtensions, ...this.workspaceDisabledExtensions]);
3683
}
3784

38-
switch (scope) {
39-
case StorageScope.GLOBAL: return this.globalDisabledExtensions;
40-
case StorageScope.WORKSPACE: return this.workspaceDisabledExtensions;
85+
if (workspace === void 0) {
86+
return this.allDisabledExtensions;
4187
}
42-
return this.allDisabledExtensions;
88+
89+
if (workspace) {
90+
return this.workspaceDisabledExtensions;
91+
}
92+
93+
return this.globalDisabledExtensions;
4394
}
4495

4596
private getDisabledExtensionsFromStorage(scope?: StorageScope): string[] {
@@ -52,8 +103,56 @@ export class ExtensionsRuntimeService implements IExtensionsRuntimeService {
52103
return [...globallyDisabled, ...workspaceDisabled];
53104
}
54105

106+
private setGlobalEnablement(identifier: string, enable: boolean, displayName: string): TPromise<boolean> {
107+
if (enable) {
108+
return this.choiceService.choose(Severity.Info, localize('enableExtensionGloballyNoWorkspace', "Would you like to enable '{0}' extension globally?", displayName),
109+
[localize('yes', "Yes"), localize('no', "No")])
110+
.then((option) => {
111+
if (option === 0) {
112+
return this.enableExtension(identifier, StorageScope.GLOBAL).then(() => true);
113+
}
114+
return TPromise.wrap(false);
115+
});
116+
} else {
117+
return this.choiceService.choose(Severity.Info, localize('disableExtensionGlobally', "Would you like to disable '{0}' extension globally?", displayName),
118+
[localize('yes', "Yes"), localize('no', "No")])
119+
.then((option) => {
120+
if (option === 0) {
121+
return this.disableExtension(identifier, StorageScope.GLOBAL).then(() => true);
122+
}
123+
return TPromise.wrap(false);
124+
});
125+
}
126+
}
127+
128+
private disableExtension(identifier: string, scope: StorageScope): TPromise<boolean> {
129+
let disabledExtensions = this._getDisabledExtensions(scope);
130+
disabledExtensions.push(identifier);
131+
this._setDisabledExtensions(disabledExtensions, scope);
132+
return TPromise.wrap(true);
133+
}
134+
135+
private enableExtension(identifier: string, scope: StorageScope): TPromise<boolean> {
136+
let disabledExtensions = this._getDisabledExtensions(scope);
137+
const index = disabledExtensions.indexOf(identifier);
138+
if (index !== -1) {
139+
disabledExtensions.splice(index, 1);
140+
this._setDisabledExtensions(disabledExtensions, scope);
141+
return TPromise.wrap(true);
142+
}
143+
return TPromise.wrap(false);
144+
}
145+
55146
private _getDisabledExtensions(scope: StorageScope): string[] {
56147
const value = this.storageService.get(DISABLED_EXTENSIONS_STORAGE_PATH, scope, '');
57148
return value ? distinct(value.split(',')) : [];
58149
}
150+
151+
private _setDisabledExtensions(disabledExtensions: string[], scope: StorageScope): void {
152+
if (disabledExtensions.length) {
153+
this.storageService.store(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions.join(','), scope);
154+
} else {
155+
this.storageService.remove(DISABLED_EXTENSIONS_STORAGE_PATH, scope);
156+
}
157+
}
59158
}

0 commit comments

Comments
 (0)