extHostExtensionService.ts 12.8 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 { IDisposable, dispose } from 'vs/base/common/lifecycle';
A
Alex Dima 已提交
8
import * as paths from 'vs/base/common/paths';
9
import { mkdirp, dirExists } from 'vs/base/node/pfs';
E
Erich Gamma 已提交
10
import Severity from 'vs/base/common/severity';
J
Johannes Rieken 已提交
11 12
import { TPromise } from 'vs/base/common/winjs.base';
import { AbstractExtensionService, ActivatedExtension } from 'vs/platform/extensions/common/abstractExtensionService';
A
Alex Dima 已提交
13
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
J
Johannes Rieken 已提交
14 15
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
16
import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl';
J
Johannes Rieken 已提交
17
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
18
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
19
import { MainContext, MainProcessExtensionServiceShape, IEnvironment, IInitData } from './extHost.protocol';
20
import { createHash } from 'crypto';
B
Benjamin Pasero 已提交
21 22

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

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

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

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

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

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

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

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

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

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

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

A
Alex Dima 已提交
73
	constructor(id: string, global: boolean, storage: ExtHostStorage) {
A
Alex Dima 已提交
74 75 76 77 78 79 80 81 82 83
		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 已提交
84
	get whenReady(): TPromise<ExtensionMemento> {
A
Alex Dima 已提交
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
		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);
	}
}

104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
class ExtensionStoragePath {

	private readonly _contextService: IWorkspaceContextService;
	private readonly _environment: IEnvironment;

	private readonly _ready: TPromise<string>;
	private _value: string;

	constructor(contextService: IWorkspaceContextService, environment: IEnvironment) {
		this._contextService = contextService;
		this._environment = environment;
		this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
	}

	get whenReady(): TPromise<any> {
		return this._ready;
	}

122 123 124 125
	value(extension: IExtensionDescription): string {
		if (this._value) {
			return paths.join(this._value, extension.id);
		}
M
Matt Bierner 已提交
126
		return undefined;
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
	}

	private _getOrCreateWorkspaceStoragePath(): TPromise<string> {

		const workspace = this._contextService.getWorkspace();

		if (!workspace) {
			return TPromise.as(undefined);
		}

		const storageName = createHash('md5')
			.update(workspace.resource.fsPath)
			.update(workspace.uid ? workspace.uid.toString() : '')
			.digest('hex');

		const storagePath = paths.join(this._environment.appSettingsHome, 'workspaceStorage', storageName);

		return dirExists(storagePath).then(exists => {
			if (exists) {
				return storagePath;
			}

J
Johannes Rieken 已提交
149
			return mkdirp(storagePath).then(success => {
150 151 152 153 154 155 156 157
				return storagePath;
			}, err => {
				return undefined;
			});
		});
	}
}

A
Alex Dima 已提交
158 159
export interface IExtensionContext {
	subscriptions: IDisposable[];
A
Alex Dima 已提交
160 161
	workspaceState: IExtensionMemento;
	globalState: IExtensionMemento;
A
Alex Dima 已提交
162
	extensionPath: string;
D
Dirk Baeumer 已提交
163
	storagePath: string;
A
Alex Dima 已提交
164 165 166
	asAbsolutePath(relativePath: string): string;
}

A
Alex Dima 已提交
167
export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExtension> {
E
Erich Gamma 已提交
168 169

	private _threadService: IThreadService;
A
Alex Dima 已提交
170
	private _storage: ExtHostStorage;
171
	private _storagePath: ExtensionStoragePath;
172
	private _proxy: MainProcessExtensionServiceShape;
173
	private _telemetryService: ITelemetryService;
174
	private _contextService: IWorkspaceContextService;
E
Erich Gamma 已提交
175 176 177 178

	/**
	 * This class is constructed manually because it is a service, so it doesn't use any ctor injection
	 */
179 180 181
	constructor(initData: IInitData, threadService: IThreadService, telemetryService: ITelemetryService, contextService: IWorkspaceContextService) {
		super(false);
		this._registry.registerExtensions(initData.extensions);
E
Erich Gamma 已提交
182
		this._threadService = threadService;
A
Alex Dima 已提交
183
		this._storage = new ExtHostStorage(threadService);
184
		this._storagePath = new ExtensionStoragePath(contextService, initData.environment);
185
		this._proxy = this._threadService.get(MainContext.MainProcessExtensionService);
186
		this._telemetryService = telemetryService;
187
		this._contextService = contextService;
188 189 190 191

		// initialize API first
		const apiFactory = createApiFactory(initData, threadService, this, this._contextService);
		initializeExtensionApi(this, apiFactory).then(() => this._triggerOnReady());
E
Erich Gamma 已提交
192 193
	}

194 195 196 197 198 199 200 201
	public getAllExtensionDescriptions(): IExtensionDescription[] {
		return this._registry.getAllExtensionDescriptions();
	}

	public getExtensionDescription(extensionId: string): IExtensionDescription {
		return this._registry.getExtensionDescription(extensionId);
	}

A
Alex Dima 已提交
202
	public $localShowMessage(severity: Severity, msg: string): void {
E
Erich Gamma 已提交
203 204 205 206 207 208 209 210 211 212 213 214
		switch (severity) {
			case Severity.Error:
				console.error(msg);
				break;
			case Severity.Warning:
				console.warn(msg);
				break;
			default:
				console.log(msg);
		}
	}

A
Alex Dima 已提交
215
	public get(extensionId: string): IExtensionAPI {
216
		if (!hasOwnProperty.call(this._activatedExtensions, extensionId)) {
A
Alex Dima 已提交
217
			throw new Error('Extension `' + extensionId + '` is not known or not activated');
218
		}
219
		return this._activatedExtensions[extensionId].exports;
220 221
	}

222
	public deactivate(extensionId: string): TPromise<void> {
J
Johannes Rieken 已提交
223
		let result: TPromise<void> = TPromise.as(void 0);
224

A
Alex Dima 已提交
225 226
		let extension = this._activatedExtensions[extensionId];
		if (!extension) {
227
			return result;
228 229 230 231
		}

		// call deactivate if available
		try {
A
Alex Dima 已提交
232
			if (typeof extension.module.deactivate === 'function') {
233 234 235 236
				result = TPromise.wrap(extension.module.deactivate()).then(null, (err) => {
					// TODO: Do something with err if this is not the shutdown case
					return TPromise.as(void 0);
				});
237
			}
B
Benjamin Pasero 已提交
238
		} catch (err) {
239 240 241 242 243
			// TODO: Do something with err if this is not the shutdown case
		}

		// clean up subscriptions
		try {
J
Joao Moreno 已提交
244
			dispose(extension.subscriptions);
B
Benjamin Pasero 已提交
245
		} catch (err) {
246 247
			// TODO: Do something with err if this is not the shutdown case
		}
248 249

		return result;
250
	}
E
Erich Gamma 已提交
251

A
Alex Dima 已提交
252 253 254 255 256
	// -- overwriting AbstractExtensionService

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

A
Alex Dima 已提交
259 260
	protected _createFailedExtension() {
		return new ExtHostExtension(true, { activate: undefined, deactivate: undefined }, undefined, []);
E
Erich Gamma 已提交
261 262
	}

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

A
Alex Dima 已提交
265 266
		let globalState = new ExtensionMemento(extensionDescription.id, true, this._storage);
		let workspaceState = new ExtensionMemento(extensionDescription.id, false, this._storage);
E
Erich Gamma 已提交
267

268 269 270 271 272
		return TPromise.join([
			globalState.whenReady,
			workspaceState.whenReady,
			this._storagePath.whenReady
		]).then(() => {
A
Alex Dima 已提交
273
			return Object.freeze(<IExtensionContext>{
E
Erich Gamma 已提交
274 275 276
				globalState,
				workspaceState,
				subscriptions: [],
A
Alex Dima 已提交
277
				get extensionPath() { return extensionDescription.extensionFolderPath; },
278
				storagePath: this._storagePath.value(extensionDescription),
A
Alex Dima 已提交
279
				asAbsolutePath: (relativePath: string) => { return paths.normalize(paths.join(extensionDescription.extensionFolderPath, relativePath), true); }
E
Erich Gamma 已提交
280 281 282 283
			});
		});
	}

284
	protected _actualActivateExtension(extensionDescription: IExtensionDescription): TPromise<ActivatedExtension> {
A
Alex Dima 已提交
285 286 287
		return this._doActualActivateExtension(extensionDescription).then((activatedExtension) => {
			this._proxy.$onExtensionActivated(extensionDescription.id);
			return activatedExtension;
E
Erich Gamma 已提交
288
		}, (err) => {
A
Alex Dima 已提交
289
			this._proxy.$onExtensionActivationFailed(extensionDescription.id);
E
Erich Gamma 已提交
290 291 292 293
			throw err;
		});
	}

A
Alex Dima 已提交
294
	private _doActualActivateExtension(extensionDescription: IExtensionDescription): TPromise<ExtHostExtension> {
295 296
		let event = getTelemetryActivationEvent(extensionDescription);
		this._telemetryService.publicLog('activatePlugin', event);
A
Alex Dima 已提交
297
		if (!extensionDescription.main) {
A
Alex Dima 已提交
298 299
			// Treat the extension as being empty => NOT AN ERROR CASE
			return TPromise.as(new ExtHostEmptyExtension());
300
		}
301 302 303 304 305 306
		return this.onReady().then(() => {
			return TPromise.join<any>([
				loadCommonJSModule(extensionDescription.main),
				this._loadExtensionContext(extensionDescription)
			]).then(values => {
				return ExtHostExtensionService._callActivate(<IExtensionModule>values[0], <IExtensionContext>values[1]);
307 308 309 310 311 312 313 314
			}, (errors: any[]) => {
				// Avoid failing with an array of errors, fail with a single error
				if (errors[0]) {
					return TPromise.wrapError(errors[0]);
				}
				if (errors[1]) {
					return TPromise.wrapError(errors[1]);
				}
M
Matt Bierner 已提交
315
				return undefined;
316 317 318 319
			});
		});
	}

A
Alex Dima 已提交
320 321 322
	private static _callActivate(extensionModule: IExtensionModule, context: IExtensionContext): TPromise<ExtHostExtension> {
		// Make sure the extension's surface is not undefined
		extensionModule = extensionModule || {
323 324 325 326
			activate: undefined,
			deactivate: undefined
		};

A
Alex Dima 已提交
327 328
		return this._callActivateOptional(extensionModule, context).then((extensionExports) => {
			return new ExtHostExtension(false, extensionModule, extensionExports, context.subscriptions);
329 330 331
		});
	}

A
Alex Dima 已提交
332 333
	private static _callActivateOptional(extensionModule: IExtensionModule, context: IExtensionContext): TPromise<IExtensionAPI> {
		if (typeof extensionModule.activate === 'function') {
334
			try {
A
Alex Dima 已提交
335
				return TPromise.as(extensionModule.activate.apply(global, [context]));
336
			} catch (err) {
A
Alex Dima 已提交
337
				return TPromise.wrapError(err);
338 339
			}
		} else {
A
Alex Dima 已提交
340 341
			// No activate found => the module is the extension's exports
			return TPromise.as<IExtensionAPI>(extensionModule);
342 343 344
		}
	}

E
Erich Gamma 已提交
345 346
	// -- called by main thread

A
Alex Dima 已提交
347
	public $activateExtension(extensionDescription: IExtensionDescription): TPromise<void> {
348
		return this._activateExtension(extensionDescription);
E
Erich Gamma 已提交
349 350 351 352
	}

}

A
Alex Dima 已提交
353
function loadCommonJSModule<T>(modulePath: string): TPromise<T> {
B
Benjamin Pasero 已提交
354
	let r: T = null;
E
Erich Gamma 已提交
355 356
	try {
		r = require.__$__nodeRequire<T>(modulePath);
B
Benjamin Pasero 已提交
357
	} catch (e) {
A
Alex Dima 已提交
358
		return TPromise.wrapError(e);
E
Erich Gamma 已提交
359
	}
A
Alex Dima 已提交
360
	return TPromise.as(r);
E
Erich Gamma 已提交
361
}
A
Alex Dima 已提交
362 363 364 365 366 367

function getTelemetryActivationEvent(extensionDescription: IExtensionDescription): any {
	let event = {
		id: extensionDescription.id,
		name: extensionDescription.name,
		publisherDisplayName: extensionDescription.publisher,
368 369
		activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
		isBuiltin: extensionDescription.isBuiltin
A
Alex Dima 已提交
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
	};

	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;
402
}