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

J
Joao Moreno 已提交
7
import {dispose} from 'vs/base/common/lifecycle';
8
import {IDisposable} from 'vs/base/common/lifecycle';
A
Alex Dima 已提交
9
import * as paths from 'vs/base/common/paths';
E
Erich Gamma 已提交
10
import Severity from 'vs/base/common/severity';
A
Alex Dima 已提交
11
import {TPromise} from 'vs/base/common/winjs.base';
12
import {AbstractExtensionService, ActivatedExtension} from 'vs/platform/extensions/common/abstractExtensionService';
13
import {IMessage, IExtensionDescription} from 'vs/platform/extensions/common/extensions';
14
import {ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry';
15
import {ExtHostStorage} from 'vs/workbench/api/node/extHostStorage';
A
Alex Dima 已提交
16
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
17
import {IThreadService} from 'vs/workbench/services/thread/common/threadService';
18 19
import {MainContext} from './extHostProtocol';
import {MainProcessExtensionService} from './mainThreadExtensionService';
B
Benjamin Pasero 已提交
20 21

const hasOwnProperty = Object.hasOwnProperty;
E
Erich Gamma 已提交
22

A
Alex Dima 已提交
23 24 25 26 27
/**
 * Represents the source code (module) of an extension.
 */
export interface IExtensionModule {
	activate(ctx: IExtensionContext): TPromise<IExtensionAPI>;
28 29 30
	deactivate(): void;
}

A
Alex Dima 已提交
31 32 33 34 35
/**
 * Represents the API of an extension (return value of `activate`).
 */
export interface IExtensionAPI {
	// _extensionAPIBrand: any;
36 37
}

A
Alex Dima 已提交
38
export class ExtHostExtension extends ActivatedExtension {
39

A
Alex Dima 已提交
40 41
	module: IExtensionModule;
	exports: IExtensionAPI;
42 43
	subscriptions: IDisposable[];

A
Alex Dima 已提交
44
	constructor(activationFailed: boolean, module: IExtensionModule, exports: IExtensionAPI, subscriptions: IDisposable[]) {
45 46 47 48
		super(activationFailed);
		this.module = module;
		this.exports = exports;
		this.subscriptions = subscriptions;
E
Erich Gamma 已提交
49
	}
50
}
E
Erich Gamma 已提交
51

A
Alex Dima 已提交
52
export class ExtHostEmptyExtension extends ExtHostExtension {
53 54
	constructor() {
		super(false, { activate: undefined, deactivate: undefined }, undefined, []);
E
Erich Gamma 已提交
55 56 57
	}
}

A
Alex Dima 已提交
58
export interface IExtensionMemento {
A
Alex Dima 已提交
59 60 61 62
	get<T>(key: string, defaultValue: T): T;
	update(key: string, value: any): Thenable<boolean>;
}

A
Alex Dima 已提交
63
class ExtensionMemento implements IExtensionMemento {
A
Alex Dima 已提交
64 65 66

	private _id: string;
	private _shared: boolean;
A
Alex Dima 已提交
67
	private _storage: ExtHostStorage;
A
Alex Dima 已提交
68

A
Alex Dima 已提交
69
	private _init: TPromise<ExtensionMemento>;
A
Alex Dima 已提交
70 71
	private _value: { [n: string]: any; };

A
Alex Dima 已提交
72
	constructor(id: string, global: boolean, storage: ExtHostStorage) {
A
Alex Dima 已提交
73 74 75 76 77 78 79 80 81 82
		this._id = id;
		this._shared = global;
		this._storage = storage;

		this._init = this._storage.getValue(this._shared, this._id, Object.create(null)).then(value => {
			this._value = value;
			return this;
		});
	}

A
Alex Dima 已提交
83
	get whenReady(): TPromise<ExtensionMemento> {
A
Alex Dima 已提交
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
		return this._init;
	}

	get<T>(key: string, defaultValue: T): T {
		let value = this._value[key];
		if (typeof value === 'undefined') {
			value = defaultValue;
		}
		return value;
	}

	update(key: string, value: any): Thenable<boolean> {
		this._value[key] = value;
		return this._storage
			.setValue(this._shared, this._id, this._value)
			.then(() => true);
	}
}

export interface IExtensionContext {
	subscriptions: IDisposable[];
A
Alex Dima 已提交
105 106
	workspaceState: IExtensionMemento;
	globalState: IExtensionMemento;
A
Alex Dima 已提交
107
	extensionPath: string;
D
Dirk Baeumer 已提交
108
	storagePath: string;
A
Alex Dima 已提交
109 110 111
	asAbsolutePath(relativePath: string): string;
}

A
Alex Dima 已提交
112
export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExtension> {
E
Erich Gamma 已提交
113 114

	private _threadService: IThreadService;
A
Alex Dima 已提交
115 116
	private _storage: ExtHostStorage;
	private _proxy: MainProcessExtensionService;
117
	private _telemetryService: ITelemetryService;
D
Dirk Baeumer 已提交
118
	private _workspaceStoragePath: string;
E
Erich Gamma 已提交
119 120 121 122

	/**
	 * This class is constructed manually because it is a service, so it doesn't use any ctor injection
	 */
D
Dirk Baeumer 已提交
123
	constructor(threadService: IThreadService, telemetryService: ITelemetryService, args: { serviceId: any; workspaceStoragePath: string; }) {
124
		super(false);
E
Erich Gamma 已提交
125
		this._threadService = threadService;
A
Alex Dima 已提交
126
		this._storage = new ExtHostStorage(threadService);
127
		this._proxy = this._threadService.get(MainContext.MainProcessExtensionService);
128
		this._telemetryService = telemetryService;
D
Dirk Baeumer 已提交
129
		this._workspaceStoragePath = args.workspaceStoragePath;
E
Erich Gamma 已提交
130 131
	}

A
Alex Dima 已提交
132
	public $localShowMessage(severity: Severity, msg: string): void {
E
Erich Gamma 已提交
133 134 135 136 137 138 139 140 141 142 143 144
		switch (severity) {
			case Severity.Error:
				console.error(msg);
				break;
			case Severity.Warning:
				console.warn(msg);
				break;
			default:
				console.log(msg);
		}
	}

A
Alex Dima 已提交
145
	public get(extensionId: string): IExtensionAPI {
146
		if (!hasOwnProperty.call(this._activatedExtensions, extensionId)) {
A
Alex Dima 已提交
147
			throw new Error('Extension `' + extensionId + '` is not known or not activated');
148
		}
149
		return this._activatedExtensions[extensionId].exports;
150 151
	}

A
Alex Dima 已提交
152
	public deactivate(extensionId: string): void {
A
Alex Dima 已提交
153 154
		let extension = this._activatedExtensions[extensionId];
		if (!extension) {
155 156 157 158 159
			return;
		}

		// call deactivate if available
		try {
A
Alex Dima 已提交
160 161
			if (typeof extension.module.deactivate === 'function') {
				extension.module.deactivate();
162
			}
B
Benjamin Pasero 已提交
163
		} catch (err) {
164 165 166 167 168
			// TODO: Do something with err if this is not the shutdown case
		}

		// clean up subscriptions
		try {
J
Joao Moreno 已提交
169
			dispose(extension.subscriptions);
B
Benjamin Pasero 已提交
170
		} catch (err) {
171 172 173
			// TODO: Do something with err if this is not the shutdown case
		}
	}
E
Erich Gamma 已提交
174

A
Alex Dima 已提交
175
	public registrationDone(messages: IMessage[]): void {
176 177 178 179 180
		this._proxy.$onExtensionHostReady(ExtensionsRegistry.getAllExtensionDescriptions(), messages).then(() => {
			// Wait for the main process to acknowledge its receival of the extensions descriptions
			// before allowing extensions to be activated
			this._triggerOnReady();
		});
A
Alex Dima 已提交
181 182
	}

A
Alex Dima 已提交
183 184 185 186 187
	// -- overwriting AbstractExtensionService

	protected _showMessage(severity: Severity, msg: string): void {
		this._proxy.$localShowMessage(severity, msg);
		this.$localShowMessage(severity, msg);
E
Erich Gamma 已提交
188 189
	}

A
Alex Dima 已提交
190 191
	protected _createFailedExtension() {
		return new ExtHostExtension(true, { activate: undefined, deactivate: undefined }, undefined, []);
E
Erich Gamma 已提交
192 193
	}

A
Alex Dima 已提交
194
	private _loadExtensionContext(extensionDescription: IExtensionDescription): TPromise<IExtensionContext> {
E
Erich Gamma 已提交
195

A
Alex Dima 已提交
196 197
		let globalState = new ExtensionMemento(extensionDescription.id, true, this._storage);
		let workspaceState = new ExtensionMemento(extensionDescription.id, false, this._storage);
D
Dirk Baeumer 已提交
198
		let storagePath = this._workspaceStoragePath ? paths.normalize(paths.join(this._workspaceStoragePath, extensionDescription.id)): undefined;
E
Erich Gamma 已提交
199

A
Alex Dima 已提交
200
		return TPromise.join([globalState.whenReady, workspaceState.whenReady]).then(() => {
A
Alex Dima 已提交
201
			return Object.freeze(<IExtensionContext>{
E
Erich Gamma 已提交
202 203 204
				globalState,
				workspaceState,
				subscriptions: [],
A
Alex Dima 已提交
205
				get extensionPath() { return extensionDescription.extensionFolderPath; },
D
Dirk Baeumer 已提交
206
				storagePath: storagePath,
A
Alex Dima 已提交
207
				asAbsolutePath: (relativePath: string) => { return paths.normalize(paths.join(extensionDescription.extensionFolderPath, relativePath), true); }
E
Erich Gamma 已提交
208 209 210 211
			});
		});
	}

212
	protected _actualActivateExtension(extensionDescription: IExtensionDescription): TPromise<ActivatedExtension> {
A
Alex Dima 已提交
213 214 215
		return this._doActualActivateExtension(extensionDescription).then((activatedExtension) => {
			this._proxy.$onExtensionActivated(extensionDescription.id);
			return activatedExtension;
E
Erich Gamma 已提交
216
		}, (err) => {
A
Alex Dima 已提交
217
			this._proxy.$onExtensionActivationFailed(extensionDescription.id);
E
Erich Gamma 已提交
218 219 220 221
			throw err;
		});
	}

A
Alex Dima 已提交
222
	private _doActualActivateExtension(extensionDescription: IExtensionDescription): TPromise<ExtHostExtension> {
223 224
		let event = getTelemetryActivationEvent(extensionDescription);
		this._telemetryService.publicLog('activatePlugin', event);
A
Alex Dima 已提交
225
		if (!extensionDescription.main) {
A
Alex Dima 已提交
226 227
			// Treat the extension as being empty => NOT AN ERROR CASE
			return TPromise.as(new ExtHostEmptyExtension());
228
		}
A
Alex Dima 已提交
229 230 231 232

		return loadCommonJSModule<IExtensionModule>(extensionDescription.main).then((extensionModule) => {
			return this._loadExtensionContext(extensionDescription).then(context => {
				return ExtHostExtensionService._callActivate(extensionModule, context);
233 234 235 236
			});
		});
	}

A
Alex Dima 已提交
237 238 239
	private static _callActivate(extensionModule: IExtensionModule, context: IExtensionContext): TPromise<ExtHostExtension> {
		// Make sure the extension's surface is not undefined
		extensionModule = extensionModule || {
240 241 242 243
			activate: undefined,
			deactivate: undefined
		};

A
Alex Dima 已提交
244 245
		return this._callActivateOptional(extensionModule, context).then((extensionExports) => {
			return new ExtHostExtension(false, extensionModule, extensionExports, context.subscriptions);
246 247 248
		});
	}

A
Alex Dima 已提交
249 250
	private static _callActivateOptional(extensionModule: IExtensionModule, context: IExtensionContext): TPromise<IExtensionAPI> {
		if (typeof extensionModule.activate === 'function') {
251
			try {
A
Alex Dima 已提交
252
				return TPromise.as(extensionModule.activate.apply(global, [context]));
253
			} catch (err) {
A
Alex Dima 已提交
254
				return TPromise.wrapError(err);
255 256
			}
		} else {
A
Alex Dima 已提交
257 258
			// No activate found => the module is the extension's exports
			return TPromise.as<IExtensionAPI>(extensionModule);
259 260 261
		}
	}

E
Erich Gamma 已提交
262 263
	// -- called by main thread

A
Alex Dima 已提交
264
	public $activateExtension(extensionDescription: IExtensionDescription): TPromise<void> {
265
		return this._activateExtension(extensionDescription);
E
Erich Gamma 已提交
266 267 268 269
	}

}

A
Alex Dima 已提交
270
function loadCommonJSModule<T>(modulePath: string): TPromise<T> {
B
Benjamin Pasero 已提交
271
	let r: T = null;
E
Erich Gamma 已提交
272 273
	try {
		r = require.__$__nodeRequire<T>(modulePath);
B
Benjamin Pasero 已提交
274
	} catch (e) {
A
Alex Dima 已提交
275
		return TPromise.wrapError(e);
E
Erich Gamma 已提交
276
	}
A
Alex Dima 已提交
277
	return TPromise.as(r);
E
Erich Gamma 已提交
278
}
A
Alex Dima 已提交
279 280 281 282 283 284

function getTelemetryActivationEvent(extensionDescription: IExtensionDescription): any {
	let event = {
		id: extensionDescription.id,
		name: extensionDescription.name,
		publisherDisplayName: extensionDescription.publisher,
285 286
		activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
		isBuiltin: extensionDescription.isBuiltin
A
Alex Dima 已提交
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
	};

	for (let contribution in extensionDescription.contributes) {
		let contributionDetails = extensionDescription.contributes[contribution];

		if (!contributionDetails) {
			continue;
		}

		switch (contribution) {
			case 'debuggers':
				let types = contributionDetails.reduce((p, c) => p ? p + ',' + c['type'] : c['type'], '');
				event['contribution.debuggers'] = types;
				break;
			case 'grammars':
				let grammers = contributionDetails.reduce((p, c) => p ? p + ',' + c['language'] : c['language'], '');
				event['contribution.grammars'] = grammers;
				break;
			case 'languages':
				let languages = contributionDetails.reduce((p, c) => p ? p + ',' + c['id'] : c['id'], '');
				event['contribution.languages'] = languages;
				break;
			case 'tmSnippets':
				let tmSnippets = contributionDetails.reduce((p, c) => p ? p + ',' + c['languageId'] : c['languageId'], '');
				event['contribution.tmSnippets'] = tmSnippets;
				break;
			default:
				event[`contribution.${contribution}`] = true;
		}
	}

	return event;
319
}