Skip to content

Commit 8102ed5

Browse files
committed
1 parent 3974c1e commit 8102ed5

7 files changed

Lines changed: 115 additions & 28 deletions

File tree

src/vs/platform/extensionManagement/common/extensionManagement.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export interface IExtensionManifest {
9898
icon?: string;
9999
categories?: string[];
100100
activationEvents?: string[];
101+
extensionDependencies: string[];
101102
contributes?: IExtensionContributions;
102103
}
103104

@@ -211,13 +212,18 @@ export interface DidInstallExtensionEvent {
211212
error?: Error;
212213
}
213214

215+
export interface DidUninstallExtensionEvent {
216+
id: string;
217+
error?: Error;
218+
}
219+
214220
export interface IExtensionManagementService {
215221
_serviceBrand: any;
216222

217223
onInstallExtension: Event<InstallExtensionEvent>;
218224
onDidInstallExtension: Event<DidInstallExtensionEvent>;
219225
onUninstallExtension: Event<string>;
220-
onDidUninstallExtension: Event<string>;
226+
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
221227

222228
install(zipPath: string): TPromise<void>;
223229
installFromGallery(extension: IGalleryExtension, promptToInstallDependencies?: boolean): TPromise<void>;

src/vs/platform/extensionManagement/common/extensionManagementIpc.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import { TPromise } from 'vs/base/common/winjs.base';
99
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
10-
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType } from './extensionManagement';
10+
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent } from './extensionManagement';
1111
import Event, { buffer } from 'vs/base/common/event';
1212

1313
export interface IExtensionManagementChannel extends IChannel {
@@ -27,7 +27,7 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel {
2727
onInstallExtension: Event<InstallExtensionEvent>;
2828
onDidInstallExtension: Event<DidInstallExtensionEvent>;
2929
onUninstallExtension: Event<string>;
30-
onDidUninstallExtension: Event<string>;
30+
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
3131

3232
constructor(private service: IExtensionManagementService) {
3333
this.onInstallExtension = buffer(service.onInstallExtension, true);
@@ -65,8 +65,8 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
6565
private _onUninstallExtension = eventFromCall<string>(this.channel, 'event:onUninstallExtension');
6666
get onUninstallExtension(): Event<string> { return this._onUninstallExtension; }
6767

68-
private _onDidUninstallExtension = eventFromCall<string>(this.channel, 'event:onDidUninstallExtension');
69-
get onDidUninstallExtension(): Event<string> { return this._onDidUninstallExtension; }
68+
private _onDidUninstallExtension = eventFromCall<DidUninstallExtensionEvent>(this.channel, 'event:onDidUninstallExtension');
69+
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this._onDidUninstallExtension; }
7070

7171
install(zipPath: string): TPromise<void> {
7272
return this.channel.call('install', zipPath);

src/vs/platform/extensionManagement/node/extensionManagementService.ts

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Promise, TPromise } from 'vs/base/common/winjs.base';
1717
import {
1818
IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
1919
IGalleryExtension, IExtensionIdentity, IExtensionManifest, IGalleryMetadata,
20-
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType
20+
InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType
2121
} from 'vs/platform/extensionManagement/common/extensionManagement';
2222
import { localizeManifest } from '../common/extensionNls';
2323
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -104,8 +104,8 @@ export class ExtensionManagementService implements IExtensionManagementService {
104104
private _onUninstallExtension = new Emitter<string>();
105105
onUninstallExtension: Event<string> = this._onUninstallExtension.event;
106106

107-
private _onDidUninstallExtension = new Emitter<string>();
108-
onDidUninstallExtension: Event<string> = this._onDidUninstallExtension.event;
107+
private _onDidUninstallExtension = new Emitter<DidUninstallExtensionEvent>();
108+
onDidUninstallExtension: Event<DidUninstallExtensionEvent> = this._onDidUninstallExtension.event;
109109

110110
constructor(
111111
@IEnvironmentService private environmentService: IEnvironmentService,
@@ -315,23 +315,75 @@ export class ExtensionManagementService implements IExtensionManagementService {
315315
return this.scanUserExtensions().then<void>(installed => {
316316
const promises = installed
317317
.filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name)
318-
.map(({ id }) => this.uninstallExtension(id));
319-
318+
.map(e => this.checkForDependenciesAndUninstall(e, installed));
320319
return TPromise.join(promises);
321320
});
322321
});
323322
}
324323

325-
private uninstallExtension(id: string): TPromise<void> {
326-
const extensionPath = path.join(this.extensionsPath, id);
324+
private checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): TPromise<void> {
325+
return this.preUninstallExtension(extension.id)
326+
.then(() => this.getDependenciesToUninstall(extension, installed)
327+
.then(dependencies => dependencies.length ? this.promptAndUninstall(extension, dependencies) : this.uninstallExtension(extension.id)))
328+
.then(() => this.postUninstallExtension(extension.id),
329+
error => {
330+
this.postUninstallExtension(extension.id, error);
331+
return TPromise.wrapError(error);
332+
});
333+
}
334+
335+
private promptAndUninstall(extension: ILocalExtension, dependencies: ILocalExtension[]): TPromise<void> {
336+
const message = nls.localize('uninstallDependeciesConfirmation', "Would you like to uninstall '{0}' only or its dependencies also?", extension.manifest.displayName);
337+
const options = [
338+
nls.localize('uninstallOnly', "Only"),
339+
nls.localize('uninstallAll', "All"),
340+
nls.localize('cancel', "Cancel")
341+
];
342+
return this.choiceService.choose(Severity.Info, message, options)
343+
.then<void>(value => {
344+
if (value === 0) {
345+
return this.doUninstall(extension.id);
346+
}
347+
if (value === 1) {
348+
return TPromise.join(dependencies.map(d => this.doUninstall(d.id)));
349+
}
350+
return TPromise.wrapError(errors.canceled());
351+
}, error => TPromise.wrapError(errors.canceled()));
352+
}
353+
354+
private getDependenciesToUninstall(extension: ILocalExtension, installed: ILocalExtension[]): TPromise<ILocalExtension[]> {
355+
if (extension.manifest.extensionDependencies && extension.manifest.extensionDependencies.length) {
356+
return TPromise.wrap(installed.filter(i => extension.manifest.extensionDependencies.indexOf(`${i.manifest.publisher}.${i.manifest.name}`) !== -1));
357+
}
358+
return TPromise.wrap([]);
359+
}
360+
361+
private doUninstall(id: string): TPromise<void> {
362+
return this.preUninstallExtension(id)
363+
.then(() => this.uninstallExtension(id))
364+
.then(() => this.postUninstallExtension(id),
365+
error => {
366+
this.postUninstallExtension(id, error);
367+
return TPromise.wrapError(error);
368+
});
369+
}
327370

371+
private preUninstallExtension(id: string): TPromise<void> {
372+
const extensionPath = path.join(this.extensionsPath, id);
328373
return pfs.exists(extensionPath)
329374
.then(exists => exists ? null : Promise.wrapError(new Error(nls.localize('notExists', "Could not find extension"))))
330-
.then(() => this._onUninstallExtension.fire(id))
331-
.then(() => this.setObsolete(id))
375+
.then(() => this._onUninstallExtension.fire(id));
376+
}
377+
378+
private uninstallExtension(id: string): TPromise<void> {
379+
const extensionPath = path.join(this.extensionsPath, id);
380+
return this.setObsolete(id)
332381
.then(() => pfs.rimraf(extensionPath))
333-
.then(() => this.unsetObsolete(id))
334-
.then(() => this._onDidUninstallExtension.fire(id));
382+
.then(() => this.unsetObsolete(id));
383+
}
384+
385+
private postUninstallExtension(id: string, error?: any): TPromise<void> {
386+
return this._onDidUninstallExtension.fire({ id, error });
335387
}
336388

337389
getInstalled(type: LocalExtensionType = null): TPromise<ILocalExtension[]> {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export enum ExtensionState {
2121
Installed,
2222
Enabled,
2323
Disabled,
24+
Uninstalling,
2425
Uninstalled
2526
}
2627

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ export class InstallAction extends Action {
8686

8787
export class UninstallAction extends Action {
8888

89+
private static UninstallLabel = localize('uninstallAction', "Uninstall");
90+
private static UninstallingLabel = localize('Uninstalling', "Uninstalling");
91+
92+
private static UninstallClass = 'extension-action uninstall';
93+
private static UnInstallingClass = 'extension-action uninstall uninstalling';
94+
8995
private disposables: IDisposable[] = [];
9096
private _extension: IExtension;
9197
get extension(): IExtension { return this._extension; }
@@ -96,7 +102,7 @@ export class UninstallAction extends Action {
96102
@IMessageService private messageService: IMessageService,
97103
@IInstantiationService private instantiationService: IInstantiationService
98104
) {
99-
super('extensions.uninstall', localize('uninstall', "Uninstall"), 'extension-action uninstall', false);
105+
super('extensions.uninstall', UninstallAction.UninstallLabel, UninstallAction.UninstallClass, false);
100106

101107
this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update()));
102108
this.update();
@@ -110,6 +116,14 @@ export class UninstallAction extends Action {
110116

111117
const state = this.extension.state;
112118

119+
if (state === ExtensionState.Uninstalling) {
120+
this.label = UninstallAction.UninstallingLabel;
121+
this.class = UninstallAction.UnInstallingClass;
122+
} else {
123+
this.label = UninstallAction.UninstallLabel;
124+
this.class = UninstallAction.UninstallClass;
125+
}
126+
113127
if (ExtensionState.Installed === state) {
114128
this.enabled = true;
115129
return;
@@ -181,6 +195,10 @@ export class CombinedInstallAction extends Action {
181195
this.enabled = false;
182196
this.label = this.installAction.label;
183197
this.class = this.installAction.class;
198+
} else if (this.extension.state === ExtensionState.Uninstalling) {
199+
this.enabled = false;
200+
this.label = this.uninstallAction.label;
201+
this.class = this.uninstallAction.class;
184202
} else {
185203
this.enabled = false;
186204
this.label = this.installAction.label;
@@ -510,7 +528,8 @@ export class ReloadAction extends Action {
510528
return;
511529
}
512530

513-
this.enabled = this.extension.reload || /* Following is needed due to extension is stale */this.extension.state === ExtensionState.Installed;
531+
const state = this.extension.state;
532+
this.enabled = state !== ExtensionState.Installing && state !== ExtensionState.Uninstalling && (this.extension.reload || /* Following is needed due to extension is stale */this.extension.state === ExtensionState.Installed);
514533
this.class = this.enabled ? ReloadAction.EnabledClass : ReloadAction.DisabledClass;
515534
}
516535

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

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
2020
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
2121
import {
2222
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest,
23-
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType
23+
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent
2424
} from 'vs/platform/extensionManagement/common/extensionManagement';
2525
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionTelemetry';
2626
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -568,11 +568,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
568568
}
569569

570570
private onUninstallExtension(id: string): void {
571-
const previousLength = this.installed.length;
572571
const extension = this.installed.filter(e => e.local.id === id)[0];
573-
this.installed = this.installed.filter(e => e.local.id !== id);
574-
575-
if (previousLength === this.installed.length) {
572+
const newLength = this.installed.filter(e => e.local.id !== id).length;
573+
// TODO: @Joao why is this?
574+
if (newLength === this.installed.length) {
576575
return;
577576
}
578577

@@ -584,17 +583,22 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
584583
this._onChange.fire();
585584
}
586585

587-
private onDidUninstallExtension(id: string): void {
586+
private onDidUninstallExtension({id, error}: DidUninstallExtensionEvent): void {
587+
if (!error) {
588+
this.installed = this.installed.filter(e => e.local.id !== id);
589+
}
590+
588591
const uninstalling = this.uninstalling.filter(e => e.id === id)[0];
589592
this.uninstalling = this.uninstalling.filter(e => e.id !== id);
590-
591593
if (!uninstalling) {
592594
return;
593595
}
594-
this.unInstalled.push(uninstalling.extension);
595-
uninstalling.extension.needsReload = true;
596-
this.reportTelemetry(uninstalling, true);
597596

597+
if (!error) {
598+
this.unInstalled.push(uninstalling.extension);
599+
uninstalling.extension.needsReload = true;
600+
this.reportTelemetry(uninstalling, true);
601+
}
598602
this._onChange.fire();
599603
}
600604

@@ -603,6 +607,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
603607
return ExtensionState.Installing;
604608
}
605609

610+
if (extension.gallery && this.uninstalling.some(e => e.extension.gallery.id === extension.gallery.id)) {
611+
return ExtensionState.Uninstalling;
612+
}
613+
606614
const disabledExtensions = this.extensionsRuntimeService.getDisabledExtensions();
607615
const local = this.installed.filter(e => e === extension || (e.gallery && extension.gallery && e.gallery.id === extension.gallery.id))[0];
608616

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484

8585

8686
.monaco-action-bar .action-item.disabled .action-label.extension-action.install:not(.installing),
87+
.monaco-action-bar .action-item.disabled .action-label.extension-action.uninstall:not(.uninstalling),
8788
.monaco-action-bar .action-item.disabled .action-label.extension-action.update,
8889
.monaco-action-bar .action-item.disabled .action-label.extension-action.enable,
8990
.monaco-action-bar .action-item.disabled .action-label.extension-action.disable,

0 commit comments

Comments
 (0)