abstractPluginService.ts 8.6 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import nls = require('vs/nls');
A
tslint  
Alex Dima 已提交
8
import {IPluginDescription, IPluginService, IMessage, IActivationEventListener, IPluginStatus } from 'vs/platform/plugins/common/plugins';
E
Erich Gamma 已提交
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
import WinJS = require('vs/base/common/winjs.base');
import {IDisposable} from 'vs/base/common/lifecycle';
import {PluginsRegistry} from 'vs/platform/plugins/common/pluginsRegistry';
import Severity from 'vs/base/common/severity';

var hasOwnProperty = Object.hasOwnProperty;

export interface IPluginContext {
	subscriptions: IDisposable[];
	workspaceState: IPluginMemento;
	globalState: IPluginMemento;
	extensionPath: string;
	asAbsolutePath(relativePath:string): string;
}

export interface IPluginMemento {
	get<T>(key: string, defaultValue: T): T;
	update(key: string, value: any): Thenable<boolean>;
}

29
export abstract class ActivatedPlugin {
E
Erich Gamma 已提交
30 31
	activationFailed: boolean;

32
	constructor(activationFailed: boolean) {
A
tslint  
Alex Dima 已提交
33
		this.activationFailed = activationFailed;
E
Erich Gamma 已提交
34 35 36
	}
}

37 38
export interface IActivatedPluginMap<T extends ActivatedPlugin> {
	[pluginId: string]: T;
E
Erich Gamma 已提交
39 40 41
}

interface IActivatingPluginMap {
42
	[pluginId: string]: WinJS.TPromise<void>;
E
Erich Gamma 已提交
43 44
}

45
export abstract class AbstractPluginService<T extends ActivatedPlugin> implements IPluginService {
E
Erich Gamma 已提交
46 47 48
	public serviceId = IPluginService;

	private activatingPlugins: IActivatingPluginMap;
49
	protected activatedPlugins: IActivatedPluginMap<T>;
E
Erich Gamma 已提交
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
	private _onReady: WinJS.TPromise<boolean>;
	private _onReadyC: (v: boolean) => void;

	constructor(isReadyByDefault:boolean) {
		if (isReadyByDefault) {
			this._onReady = WinJS.TPromise.as(true);
			this._onReadyC = (v: boolean) => { /*No-op*/ };
		} else {
			this._onReady = new WinJS.TPromise<boolean>((c, e, p) => {
				this._onReadyC = c;
			}, () => {
				console.warn('You should really not try to cancel this ready promise!');
			});
		}
		this.activatingPlugins = {};
		this.activatedPlugins = {};
	}

68
	public abstract deactivate(pluginId:string): void;
E
Erich Gamma 已提交
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
	protected abstract _showMessage(severity:Severity, message:string): void;

	protected showMessage(severity:Severity, source:string, message:string): void {
		this._showMessage(severity, ( source ? '[' + source + ']: ' : '') + message);
	}

	public registrationDone(messages: IMessage[]): void {
		messages.forEach((entry) => {
			this.showMessage(entry.type, entry.source, entry.message);
		});
		this._onReadyC(true);
	}

	public registerOneTimeActivationEventListener(activationEvent: string, listener: IActivationEventListener): void {
		PluginsRegistry.registerOneTimeActivationEventListener(activationEvent, listener);
	}

	public onReady(): WinJS.TPromise<boolean> {
		return this._onReady;
	}


I
isidor 已提交
91 92 93 94
	public getPluginsStatus(): { [id: string]: IPluginStatus } {
		return null;
	}

E
Erich Gamma 已提交
95 96 97 98 99 100 101 102 103 104 105 106
	public isActivated(pluginId:string): boolean {
		return hasOwnProperty.call(this.activatedPlugins, pluginId);
	}

	public activateByEvent(activationEvent: string): WinJS.TPromise<void> {
		return this._onReady.then(() => {
			PluginsRegistry.triggerActivationEventListeners(activationEvent);
			let activatePlugins = PluginsRegistry.getPluginDescriptionsForActivationEvent(activationEvent);
			return this._activatePlugins(activatePlugins, 0);
		});
	}

107
	public activateAndGet(pluginId: string): WinJS.TPromise<void> {
E
Erich Gamma 已提交
108 109 110 111 112 113
		return this._onReady.then(() => {
			var desc = PluginsRegistry.getPluginDescription(pluginId);
			if (!desc) {
				throw new Error('Plugin `' + pluginId + '` is not known');
			}

114
			return this._activatePlugins([desc], 0);
E
Erich Gamma 已提交
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
		});
	}

	/**
	 * Handle semantics related to dependencies for `currentPlugin`.
	 * semantics: `redExtensions` must wait for `greenExtensions`.
	 */
	private _handleActivateRequest(currentPlugin:IPluginDescription, greenExtensions: { [id:string]: IPluginDescription; }, redExtensions: IPluginDescription[]): void {
		let depIds = (typeof currentPlugin.extensionDependencies === 'undefined' ? [] : currentPlugin.extensionDependencies);
		let currentPluginGetsGreenLight = true;

		for (let j = 0, lenJ = depIds.length; j < lenJ; j++) {
			let depId = depIds[j];
			let depDesc = PluginsRegistry.getPluginDescription(depId);

			if (!depDesc) {
				// Error condition 1: unknown dependency
				this._showMessage(Severity.Error, nls.localize('unknownDep', "Extension `{1}` failed to activate. Reason: unknown dependency `{0}`.", depId, currentPlugin.id));
133
				this.activatedPlugins[currentPlugin.id] = this._createFailedPlugin();
E
Erich Gamma 已提交
134 135 136 137 138 139 140 141
				return;
			}

			if (hasOwnProperty.call(this.activatedPlugins, depId)) {
				let dep = this.activatedPlugins[depId];
				if (dep.activationFailed) {
					// Error condition 2: a dependency has already failed activation
					this._showMessage(Severity.Error, nls.localize('failedDep', "Extension `{1}` failed to activate. Reason: dependency `{0}` failed to activate.", depId, currentPlugin.id));
142
					this.activatedPlugins[currentPlugin.id] = this._createFailedPlugin();
E
Erich Gamma 已提交
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
					return;
				}
			} else {
				// must first wait for the dependency to activate
				currentPluginGetsGreenLight = false;
				greenExtensions[depId] = depDesc;
			}
		}

		if (currentPluginGetsGreenLight) {
			greenExtensions[currentPlugin.id] = currentPlugin;
		} else {
			redExtensions.push(currentPlugin);
		}
	}

	private _activatePlugins(pluginDescriptions: IPluginDescription[], recursionLevel:number): WinJS.TPromise<void> {
		// console.log(recursionLevel, '_activatePlugins: ', pluginDescriptions.map(p => p.id));
		if (pluginDescriptions.length === 0) {
			return WinJS.TPromise.as(void 0);
		}

		pluginDescriptions = pluginDescriptions.filter((p) => !hasOwnProperty.call(this.activatedPlugins, p.id));
		if (pluginDescriptions.length === 0) {
			return WinJS.TPromise.as(void 0);
		}

		if (recursionLevel > 10) {
			// More than 10 dependencies deep => most likely a dependency loop
			for (let i = 0, len = pluginDescriptions.length; i < len; i++) {
				// Error condition 3: dependency loop
				this._showMessage(Severity.Error, nls.localize('failedDep', "Extension `{0}` failed to activate. Reason: more than 10 levels of dependencies (most likely a dependency loop).", pluginDescriptions[i].id));
175
				this.activatedPlugins[pluginDescriptions[i].id] = this._createFailedPlugin();
E
Erich Gamma 已提交
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
			}
			return WinJS.TPromise.as(void 0);
		}

		let greenMap: { [id:string]: IPluginDescription; } = Object.create(null),
			red: IPluginDescription[] = [];

		for (let i = 0, len = pluginDescriptions.length; i < len; i++) {
			this._handleActivateRequest(pluginDescriptions[i], greenMap, red);
		}

		// Make sure no red is also green
		for (let i = 0, len = red.length; i < len; i++) {
			if (greenMap[red[i].id]) {
				delete greenMap[red[i].id];
			}
		}

		let green = Object.keys(greenMap).map(id => greenMap[id]);

		// console.log('greenExtensions: ', green.map(p => p.id));
		// console.log('redExtensions: ', red.map(p => p.id));

		if (red.length === 0) {
			// Finally reached only leafs!
			return WinJS.TPromise.join(green.map((p) => this._activatePlugin(p))).then(_ => void 0);
		}

		return this._activatePlugins(green, recursionLevel + 1).then(_ => {
			return this._activatePlugins(red, recursionLevel + 1);
		});
	}

209
	protected _activatePlugin(pluginDescription: IPluginDescription): WinJS.TPromise<void> {
E
Erich Gamma 已提交
210
		if (hasOwnProperty.call(this.activatedPlugins, pluginDescription.id)) {
211
			return WinJS.TPromise.as(void 0);
E
Erich Gamma 已提交
212 213 214 215 216 217 218 219 220 221 222
		}

		if (hasOwnProperty.call(this.activatingPlugins, pluginDescription.id)) {
			return this.activatingPlugins[pluginDescription.id];
		}

		this.activatingPlugins[pluginDescription.id] = this._actualActivatePlugin(pluginDescription).then(null, (err) => {
			this._showMessage(Severity.Error, nls.localize('activationError', "Activating extension `{0}` failed: {1}.", pluginDescription.id, err.message));
			console.error('Activating extension `' + pluginDescription.id + '` failed: ', err.message);
			console.log('Here is the error stack: ', err.stack);
			// Treat the plugin as being empty
223 224
			return this._createFailedPlugin();
		}).then((x:T) => {
E
Erich Gamma 已提交
225 226 227 228 229 230 231
			this.activatedPlugins[pluginDescription.id] = x;
			delete this.activatingPlugins[pluginDescription.id];
		});

		return this.activatingPlugins[pluginDescription.id];
	}

232
	protected abstract _createFailedPlugin(): T;
E
Erich Gamma 已提交
233

234
	protected abstract _actualActivatePlugin(pluginDescription: IPluginDescription): WinJS.TPromise<T>;
E
Erich Gamma 已提交
235 236 237 238 239 240 241 242 243
}

export function loadAMDModule<T>(moduleId: string): WinJS.TPromise<T> {
	return new WinJS.TPromise<T>((c, e, p) => {
		require([moduleId], (r: T) => {
			c(r);
		}, e);
	});
}