extHostExtensionService.ts 10.7 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
Johannes Rieken 已提交
7 8
import { dispose } from 'vs/base/common/lifecycle';
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';
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 16 17 18
import { ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry';
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
import { MainContext, MainProcessExtensionServiceShape } 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 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);
	}
}

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

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

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

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

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

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

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

A
Alex Dima 已提交
154 155
		let extension = this._activatedExtensions[extensionId];
		if (!extension) {
156
			return result;
157 158 159 160
		}

		// call deactivate if available
		try {
A
Alex Dima 已提交
161
			if (typeof extension.module.deactivate === 'function') {
162 163 164 165
				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);
				});
166
			}
B
Benjamin Pasero 已提交
167
		} catch (err) {
168 169 170 171 172
			// TODO: Do something with err if this is not the shutdown case
		}

		// clean up subscriptions
		try {
J
Joao Moreno 已提交
173
			dispose(extension.subscriptions);
B
Benjamin Pasero 已提交
174
		} catch (err) {
175 176
			// TODO: Do something with err if this is not the shutdown case
		}
177 178

		return result;
179
	}
E
Erich Gamma 已提交
180

A
Alex Dima 已提交
181 182
	public registrationDone(): void {
		this._proxy.$onExtensionHostReady(ExtensionsRegistry.getAllExtensionDescriptions()).then(() => {
183 184 185 186
			// Wait for the main process to acknowledge its receival of the extensions descriptions
			// before allowing extensions to be activated
			this._triggerOnReady();
		});
A
Alex Dima 已提交
187 188
	}

A
Alex Dima 已提交
189 190 191 192 193
	// -- overwriting AbstractExtensionService

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

A
Alex Dima 已提交
196 197
	protected _createFailedExtension() {
		return new ExtHostExtension(true, { activate: undefined, deactivate: undefined }, undefined, []);
E
Erich Gamma 已提交
198 199
	}

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

A
Alex Dima 已提交
202 203
		let globalState = new ExtensionMemento(extensionDescription.id, true, this._storage);
		let workspaceState = new ExtensionMemento(extensionDescription.id, false, this._storage);
J
Johannes Rieken 已提交
204
		let storagePath = this._workspaceStoragePath ? paths.normalize(paths.join(this._workspaceStoragePath, extensionDescription.id)) : undefined;
E
Erich Gamma 已提交
205

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

218
	protected _actualActivateExtension(extensionDescription: IExtensionDescription): TPromise<ActivatedExtension> {
A
Alex Dima 已提交
219 220 221
		return this._doActualActivateExtension(extensionDescription).then((activatedExtension) => {
			this._proxy.$onExtensionActivated(extensionDescription.id);
			return activatedExtension;
E
Erich Gamma 已提交
222
		}, (err) => {
A
Alex Dima 已提交
223
			this._proxy.$onExtensionActivationFailed(extensionDescription.id);
E
Erich Gamma 已提交
224 225 226 227
			throw err;
		});
	}

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

		return loadCommonJSModule<IExtensionModule>(extensionDescription.main).then((extensionModule) => {
			return this._loadExtensionContext(extensionDescription).then(context => {
				return ExtHostExtensionService._callActivate(extensionModule, context);
239 240 241 242
			});
		});
	}

A
Alex Dima 已提交
243 244 245
	private static _callActivate(extensionModule: IExtensionModule, context: IExtensionContext): TPromise<ExtHostExtension> {
		// Make sure the extension's surface is not undefined
		extensionModule = extensionModule || {
246 247 248 249
			activate: undefined,
			deactivate: undefined
		};

A
Alex Dima 已提交
250 251
		return this._callActivateOptional(extensionModule, context).then((extensionExports) => {
			return new ExtHostExtension(false, extensionModule, extensionExports, context.subscriptions);
252 253 254
		});
	}

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

E
Erich Gamma 已提交
268 269
	// -- called by main thread

A
Alex Dima 已提交
270
	public $activateExtension(extensionDescription: IExtensionDescription): TPromise<void> {
271
		return this._activateExtension(extensionDescription);
E
Erich Gamma 已提交
272 273 274 275
	}

}

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

function getTelemetryActivationEvent(extensionDescription: IExtensionDescription): any {
	let event = {
		id: extensionDescription.id,
		name: extensionDescription.name,
		publisherDisplayName: extensionDescription.publisher,
291 292
		activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
		isBuiltin: extensionDescription.isBuiltin
A
Alex Dima 已提交
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 319 320 321 322 323 324
	};

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