extHostExtensionService.ts 12.3 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';
8
import { join } from 'path';
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 { MainContext, MainProcessExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData } from './extHost.protocol';
B
Benjamin Pasero 已提交
19 20

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

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

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

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

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

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

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

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

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

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

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

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

102 103
class ExtensionStoragePath {

104
	private readonly _workspace: IWorkspaceData;
105 106 107 108 109
	private readonly _environment: IEnvironment;

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

110
	constructor(workspace: IWorkspaceData, environment: IEnvironment) {
111
		this._workspace = workspace;
112 113 114 115 116 117 118 119
		this._environment = environment;
		this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
	}

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

120 121
	value(extension: IExtensionDescription): string {
		if (this._value) {
122
			return join(this._value, extension.id);
123
		}
M
Matt Bierner 已提交
124
		return undefined;
125 126 127
	}

	private _getOrCreateWorkspaceStoragePath(): TPromise<string> {
128
		if (!this._workspace) {
129 130
			return TPromise.as(undefined);
		}
131
		const storageName = this._workspace.id;
132
		const storagePath = join(this._environment.appSettingsHome, 'workspaceStorage', storageName);
133 134 135 136 137 138

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

J
Johannes Rieken 已提交
139
			return mkdirp(storagePath).then(success => {
140 141 142 143 144 145 146 147
				return storagePath;
			}, err => {
				return undefined;
			});
		});
	}
}

A
Alex Dima 已提交
148 149
export interface IExtensionContext {
	subscriptions: IDisposable[];
A
Alex Dima 已提交
150 151
	workspaceState: IExtensionMemento;
	globalState: IExtensionMemento;
A
Alex Dima 已提交
152
	extensionPath: string;
D
Dirk Baeumer 已提交
153
	storagePath: string;
A
Alex Dima 已提交
154 155 156
	asAbsolutePath(relativePath: string): string;
}

A
Alex Dima 已提交
157
export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExtension> {
E
Erich Gamma 已提交
158 159

	private _threadService: IThreadService;
A
Alex Dima 已提交
160
	private _storage: ExtHostStorage;
161
	private _storagePath: ExtensionStoragePath;
162
	private _proxy: MainProcessExtensionServiceShape;
163
	private _telemetryService: ITelemetryService;
E
Erich Gamma 已提交
164 165 166 167

	/**
	 * This class is constructed manually because it is a service, so it doesn't use any ctor injection
	 */
168
	constructor(initData: IInitData, threadService: IThreadService, telemetryService: ITelemetryService) {
169 170
		super(false);
		this._registry.registerExtensions(initData.extensions);
E
Erich Gamma 已提交
171
		this._threadService = threadService;
A
Alex Dima 已提交
172
		this._storage = new ExtHostStorage(threadService);
173
		this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment);
174
		this._proxy = this._threadService.get(MainContext.MainProcessExtensionService);
175
		this._telemetryService = telemetryService;
176 177

		// initialize API first
178
		const apiFactory = createApiFactory(initData, threadService, this, this._telemetryService);
179
		initializeExtensionApi(this, apiFactory).then(() => this._triggerOnReady());
E
Erich Gamma 已提交
180 181
	}

182 183 184 185 186 187 188 189
	public getAllExtensionDescriptions(): IExtensionDescription[] {
		return this._registry.getAllExtensionDescriptions();
	}

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

A
Alex Dima 已提交
190
	public $localShowMessage(severity: Severity, msg: string): void {
E
Erich Gamma 已提交
191 192 193 194 195 196 197 198 199 200 201 202
		switch (severity) {
			case Severity.Error:
				console.error(msg);
				break;
			case Severity.Warning:
				console.warn(msg);
				break;
			default:
				console.log(msg);
		}
	}

A
Alex Dima 已提交
203
	public get(extensionId: string): IExtensionAPI {
204
		if (!hasOwnProperty.call(this._activatedExtensions, extensionId)) {
A
Alex Dima 已提交
205
			throw new Error('Extension `' + extensionId + '` is not known or not activated');
206
		}
207
		return this._activatedExtensions[extensionId].exports;
208 209
	}

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

A
Alex Dima 已提交
213 214
		let extension = this._activatedExtensions[extensionId];
		if (!extension) {
215
			return result;
216 217 218 219
		}

		// call deactivate if available
		try {
A
Alex Dima 已提交
220
			if (typeof extension.module.deactivate === 'function') {
221 222 223 224
				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);
				});
225
			}
B
Benjamin Pasero 已提交
226
		} catch (err) {
227 228 229 230 231
			// TODO: Do something with err if this is not the shutdown case
		}

		// clean up subscriptions
		try {
J
Joao Moreno 已提交
232
			dispose(extension.subscriptions);
B
Benjamin Pasero 已提交
233
		} catch (err) {
234 235
			// TODO: Do something with err if this is not the shutdown case
		}
236 237

		return result;
238
	}
E
Erich Gamma 已提交
239

A
Alex Dima 已提交
240 241 242 243 244
	// -- overwriting AbstractExtensionService

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

A
Alex Dima 已提交
247 248
	protected _createFailedExtension() {
		return new ExtHostExtension(true, { activate: undefined, deactivate: undefined }, undefined, []);
E
Erich Gamma 已提交
249 250
	}

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

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

256 257 258 259 260
		return TPromise.join([
			globalState.whenReady,
			workspaceState.whenReady,
			this._storagePath.whenReady
		]).then(() => {
A
Alex Dima 已提交
261
			return Object.freeze(<IExtensionContext>{
E
Erich Gamma 已提交
262 263 264
				globalState,
				workspaceState,
				subscriptions: [],
A
Alex Dima 已提交
265
				get extensionPath() { return extensionDescription.extensionFolderPath; },
266
				storagePath: this._storagePath.value(extensionDescription),
267
				asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionFolderPath, relativePath); }
E
Erich Gamma 已提交
268 269 270 271
			});
		});
	}

272
	protected _actualActivateExtension(extensionDescription: IExtensionDescription): TPromise<ExtHostExtension> {
A
Alex Dima 已提交
273 274 275
		return this._doActualActivateExtension(extensionDescription).then((activatedExtension) => {
			this._proxy.$onExtensionActivated(extensionDescription.id);
			return activatedExtension;
E
Erich Gamma 已提交
276
		}, (err) => {
A
Alex Dima 已提交
277
			this._proxy.$onExtensionActivationFailed(extensionDescription.id);
E
Erich Gamma 已提交
278 279 280 281
			throw err;
		});
	}

A
Alex Dima 已提交
282
	private _doActualActivateExtension(extensionDescription: IExtensionDescription): TPromise<ExtHostExtension> {
283 284
		let event = getTelemetryActivationEvent(extensionDescription);
		this._telemetryService.publicLog('activatePlugin', event);
A
Alex Dima 已提交
285
		if (!extensionDescription.main) {
A
Alex Dima 已提交
286 287
			// Treat the extension as being empty => NOT AN ERROR CASE
			return TPromise.as(new ExtHostEmptyExtension());
288
		}
289 290 291 292 293 294
		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]);
295 296 297
			}, (errors: any[]) => {
				// Avoid failing with an array of errors, fail with a single error
				if (errors[0]) {
298
					return TPromise.wrapError<ExtHostExtension>(errors[0]);
299 300
				}
				if (errors[1]) {
301
					return TPromise.wrapError<ExtHostExtension>(errors[1]);
302
				}
M
Matt Bierner 已提交
303
				return undefined;
304 305 306 307
			});
		});
	}

A
Alex Dima 已提交
308 309 310
	private static _callActivate(extensionModule: IExtensionModule, context: IExtensionContext): TPromise<ExtHostExtension> {
		// Make sure the extension's surface is not undefined
		extensionModule = extensionModule || {
311 312 313 314
			activate: undefined,
			deactivate: undefined
		};

A
Alex Dima 已提交
315 316
		return this._callActivateOptional(extensionModule, context).then((extensionExports) => {
			return new ExtHostExtension(false, extensionModule, extensionExports, context.subscriptions);
317 318 319
		});
	}

A
Alex Dima 已提交
320 321
	private static _callActivateOptional(extensionModule: IExtensionModule, context: IExtensionContext): TPromise<IExtensionAPI> {
		if (typeof extensionModule.activate === 'function') {
322
			try {
A
Alex Dima 已提交
323
				return TPromise.as(extensionModule.activate.apply(global, [context]));
324
			} catch (err) {
A
Alex Dima 已提交
325
				return TPromise.wrapError(err);
326 327
			}
		} else {
A
Alex Dima 已提交
328 329
			// No activate found => the module is the extension's exports
			return TPromise.as<IExtensionAPI>(extensionModule);
330 331 332
		}
	}

E
Erich Gamma 已提交
333 334
	// -- called by main thread

A
Alex Dima 已提交
335
	public $activateExtension(extensionDescription: IExtensionDescription): TPromise<void> {
336
		return this._activateExtension(extensionDescription);
E
Erich Gamma 已提交
337 338 339 340
	}

}

A
Alex Dima 已提交
341
function loadCommonJSModule<T>(modulePath: string): TPromise<T> {
B
Benjamin Pasero 已提交
342
	let r: T = null;
E
Erich Gamma 已提交
343 344
	try {
		r = require.__$__nodeRequire<T>(modulePath);
B
Benjamin Pasero 已提交
345
	} catch (e) {
346
		return TPromise.wrapError<T>(e);
E
Erich Gamma 已提交
347
	}
A
Alex Dima 已提交
348
	return TPromise.as(r);
E
Erich Gamma 已提交
349
}
A
Alex Dima 已提交
350 351 352 353 354 355

function getTelemetryActivationEvent(extensionDescription: IExtensionDescription): any {
	let event = {
		id: extensionDescription.id,
		name: extensionDescription.name,
		publisherDisplayName: extensionDescription.publisher,
356 357
		activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
		isBuiltin: extensionDescription.isBuiltin
A
Alex Dima 已提交
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
	};

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