extensionsRegistry.ts 12.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';

A
Alex Dima 已提交
7
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
8 9
import { onUnexpectedError } from 'vs/base/common/errors';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
E
Erich Gamma 已提交
10
import Severity from 'vs/base/common/severity';
11
import { IMessage, IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
J
Johannes Rieken 已提交
12
import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
13
import { Registry } from 'vs/platform/registry/common/platform';
S
Sandeep Somavarapu 已提交
14
import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement';
15

16
const hasOwnProperty = Object.hasOwnProperty;
17
const schemaRegistry = Registry.as<IJSONContributionRegistry>(Extensions.JSONContribution);
E
Erich Gamma 已提交
18

19
export class ExtensionMessageCollector {
E
Erich Gamma 已提交
20

21 22 23
	private readonly _messageHandler: (msg: IMessage) => void;
	private readonly _extension: IExtensionDescription;
	private readonly _extensionPointId: string;
E
Erich Gamma 已提交
24

25 26 27 28 29
	constructor(
		messageHandler: (msg: IMessage) => void,
		extension: IExtensionDescription,
		extensionPointId: string
	) {
A
Alex Dima 已提交
30
		this._messageHandler = messageHandler;
31 32
		this._extension = extension;
		this._extensionPointId = extensionPointId;
E
Erich Gamma 已提交
33 34
	}

J
Johannes Rieken 已提交
35
	private _msg(type: Severity, message: string): void {
A
Alex Dima 已提交
36
		this._messageHandler({
E
Erich Gamma 已提交
37
			type: type,
A
Alex Dima 已提交
38
			message: message,
39 40
			extensionId: this._extension.id,
			extensionPointId: this._extensionPointId
E
Erich Gamma 已提交
41 42 43
		});
	}

A
Alex Dima 已提交
44 45
	public error(message: string): void {
		this._msg(Severity.Error, message);
E
Erich Gamma 已提交
46 47
	}

A
Alex Dima 已提交
48 49
	public warn(message: string): void {
		this._msg(Severity.Warning, message);
E
Erich Gamma 已提交
50 51
	}

A
Alex Dima 已提交
52 53
	public info(message: string): void {
		this._msg(Severity.Info, message);
E
Erich Gamma 已提交
54 55 56 57
	}
}

export interface IExtensionPointUser<T> {
A
Alex Dima 已提交
58
	description: IExtensionDescription;
E
Erich Gamma 已提交
59
	value: T;
60
	collector: ExtensionMessageCollector;
E
Erich Gamma 已提交
61 62 63
}

export interface IExtensionPointHandler<T> {
B
Benjamin Pasero 已提交
64
	(extensions: IExtensionPointUser<T>[]): void;
E
Erich Gamma 已提交
65 66 67 68 69 70 71
}

export interface IExtensionPoint<T> {
	name: string;
	setHandler(handler: IExtensionPointHandler<T>): void;
}

A
Alex Dima 已提交
72
export class ExtensionPoint<T> implements IExtensionPoint<T> {
E
Erich Gamma 已提交
73

A
Alex Dima 已提交
74
	public readonly name: string;
E
Erich Gamma 已提交
75
	private _handler: IExtensionPointHandler<T>;
A
Alex Dima 已提交
76 77
	private _users: IExtensionPointUser<T>[];
	private _done: boolean;
E
Erich Gamma 已提交
78

A
Alex Dima 已提交
79
	constructor(name: string) {
E
Erich Gamma 已提交
80 81
		this.name = name;
		this._handler = null;
A
Alex Dima 已提交
82 83
		this._users = null;
		this._done = false;
E
Erich Gamma 已提交
84 85 86
	}

	setHandler(handler: IExtensionPointHandler<T>): void {
A
Alex Dima 已提交
87
		if (this._handler !== null || this._done) {
E
Erich Gamma 已提交
88 89 90 91 92 93
			throw new Error('Handler already set!');
		}
		this._handler = handler;
		this._handle();
	}

A
Alex Dima 已提交
94 95 96 97 98
	acceptUsers(users: IExtensionPointUser<T>[]): void {
		if (this._users !== null || this._done) {
			throw new Error('Users already set!');
		}
		this._users = users;
E
Erich Gamma 已提交
99 100 101 102
		this._handle();
	}

	private _handle(): void {
A
Alex Dima 已提交
103
		if (this._handler === null || this._users === null) {
E
Erich Gamma 已提交
104 105
			return;
		}
A
Alex Dima 已提交
106
		this._done = true;
E
Erich Gamma 已提交
107

A
Alex Dima 已提交
108 109 110 111 112 113 114 115 116 117 118
		let handler = this._handler;
		this._handler = null;

		let users = this._users;
		this._users = null;

		try {
			handler(users);
		} catch (err) {
			onUnexpectedError(err);
		}
E
Erich Gamma 已提交
119 120 121
	}
}

J
Joao Moreno 已提交
122 123 124
const schemaId = 'vscode://schemas/vscode-extensions';
const schema: IJSONSchema = {
	properties: {
125
		engines: {
126
			type: 'object',
J
Joao Moreno 已提交
127
			description: nls.localize('vscode.extension.engines', "Engine compatibility."),
128 129 130 131
			properties: {
				'vscode': {
					type: 'string',
					description: nls.localize('vscode.extension.engines.vscode', 'For VS Code extensions, specifies the VS Code version that the extension is compatible with. Cannot be *. For example: ^0.10.5 indicates compatibility with a minimum VS Code version of 0.10.5.'),
132
					default: '^1.22.0',
133 134 135
				}
			}
		},
D
Denis Malinochkin 已提交
136 137 138 139
		publisher: {
			description: nls.localize('vscode.extension.publisher', 'The publisher of the VS Code extension.'),
			type: 'string'
		},
J
Joao Moreno 已提交
140 141 142 143 144 145 146
		displayName: {
			description: nls.localize('vscode.extension.displayName', 'The display name for the extension used in the VS Code gallery.'),
			type: 'string'
		},
		categories: {
			description: nls.localize('vscode.extension.categories', 'The categories used by the VS Code gallery to categorize the extension.'),
			type: 'array',
147
			uniqueItems: true,
J
Joao Moreno 已提交
148
			items: {
149 150 151 152 153 154 155 156 157
				oneOf: [{
					type: 'string',
					enum: ['Programming Languages', 'Snippets', 'Linters', 'Themes', 'Debuggers', 'Other', 'Keymaps', 'Formatters', 'Extension Packs', 'SCM Providers', 'Azure', 'Language Packs'],
				},
				{
					type: 'string',
					const: 'Languages',
					deprecationMessage: nls.localize('vscode.extension.category.languages.deprecated', 'Use \'Programming  Languages\' instead'),
				}]
J
Joao Moreno 已提交
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
			}
		},
		galleryBanner: {
			type: 'object',
			description: nls.localize('vscode.extension.galleryBanner', 'Banner used in the VS Code marketplace.'),
			properties: {
				color: {
					description: nls.localize('vscode.extension.galleryBanner.color', 'The banner color on the VS Code marketplace page header.'),
					type: 'string'
				},
				theme: {
					description: nls.localize('vscode.extension.galleryBanner.theme', 'The color theme for the font used in the banner.'),
					type: 'string',
					enum: ['dark', 'light']
				}
			}
		},
D
Denis Malinochkin 已提交
175 176 177 178 179 180 181
		contributes: {
			description: nls.localize('vscode.extension.contributes', 'All contributions of the VS Code extension represented by this package.'),
			type: 'object',
			properties: {
				// extensions will fill in
			},
			default: {}
J
Joao Moreno 已提交
182
		},
D
Denis Malinochkin 已提交
183 184 185 186
		preview: {
			type: 'boolean',
			description: nls.localize('vscode.extension.preview', 'Sets the extension to be flagged as a Preview in the Marketplace.'),
		},
J
Joao Moreno 已提交
187 188 189 190
		activationEvents: {
			description: nls.localize('vscode.extension.activationEvents', 'Activation events for the VS Code extension.'),
			type: 'array',
			items: {
191
				type: 'string',
192
				defaultSnippets: [
193 194 195 196 197 198 199 200 201 202 203 204
					{
						label: 'onLanguage',
						description: nls.localize('vscode.extension.activationEvents.onLanguage', 'An activation event emitted whenever a file that resolves to the specified language gets opened.'),
						body: 'onLanguage:${1:languageId}'
					},
					{
						label: 'onCommand',
						description: nls.localize('vscode.extension.activationEvents.onCommand', 'An activation event emitted whenever the specified command gets invoked.'),
						body: 'onCommand:${2:commandId}'
					},
					{
						label: 'onDebug',
205 206
						description: nls.localize('vscode.extension.activationEvents.onDebug', 'An activation event emitted whenever a user is about to start debugging or about to setup debug configurations.'),
						body: 'onDebug'
207
					},
208 209 210 211 212 213 214 215 216 217
					{
						label: 'onDebugInitialConfigurations',
						description: nls.localize('vscode.extension.activationEvents.onDebugInitialConfigurations', 'An activation event emitted whenever a "launch.json" needs to be created (and all provideDebugConfigurations methods need to be called).'),
						body: 'onDebugInitialConfigurations'
					},
					{
						label: 'onDebugResolve',
						description: nls.localize('vscode.extension.activationEvents.onDebugResolve', 'An activation event emitted whenever a debug session with the specific type is about to be launched (and a corresponding resolveDebugConfiguration method needs to be called).'),
						body: 'onDebugResolve:${6:type}'
					},
218 219 220 221 222
					{
						label: 'workspaceContains',
						description: nls.localize('vscode.extension.activationEvents.workspaceContains', 'An activation event emitted whenever a folder is opened that contains at least a file matching the specified glob pattern.'),
						body: 'workspaceContains:${4:filePattern}'
					},
223 224 225 226 227
					{
						label: 'onSearch',
						description: nls.localize('vscode.extension.activationEvents.onSearch', 'An activation event emitted whenever a search is started in the folder with the given scheme.'),
						body: 'onSearch:${7:scheme}'
					},
228 229
					{
						label: 'onView',
S
Sandeep Somavarapu 已提交
230 231
						body: 'onView:${5:viewId}',
						description: nls.localize('vscode.extension.activationEvents.onView', 'An activation event emitted whenever the specified view is expanded.'),
232
					},
J
onUri  
Joao Moreno 已提交
233 234
					{
						label: 'onUri',
J
Joao Moreno 已提交
235
						body: 'onUri',
J
onUri  
Joao Moreno 已提交
236 237
						description: nls.localize('vscode.extension.activationEvents.onUri', 'An activation event emitted whenever a system-wide Uri directed towards this extension is open.'),
					},
238 239 240 241 242
					{
						label: '*',
						description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'),
						body: '*'
					}
243
				],
J
Joao Moreno 已提交
244 245
			}
		},
D
Denis Malinochkin 已提交
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
		badges: {
			type: 'array',
			description: nls.localize('vscode.extension.badges', 'Array of badges to display in the sidebar of the Marketplace\'s extension page.'),
			items: {
				type: 'object',
				required: ['url', 'href', 'description'],
				properties: {
					url: {
						type: 'string',
						description: nls.localize('vscode.extension.badges.url', 'Badge image URL.')
					},
					href: {
						type: 'string',
						description: nls.localize('vscode.extension.badges.href', 'Badge link.')
					},
					description: {
						type: 'string',
						description: nls.localize('vscode.extension.badges.description', 'Badge description.')
					}
				}
			}
		},
J
Joao Moreno 已提交
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
		markdown: {
			type: 'string',
			description: nls.localize('vscode.extension.markdown', "Controls the Markdown rendering engine used in the Marketplace. Either github (default) or standard."),
			enum: ['github', 'standard'],
			default: 'github'
		},
		qna: {
			default: 'marketplace',
			description: nls.localize('vscode.extension.qna', "Controls the Q&A link in the Marketplace. Set to marketplace to enable the default Marketplace Q & A site. Set to a string to provide the URL of a custom Q & A site. Set to false to disable Q & A altogether."),
			anyOf: [
				{
					type: ['string', 'boolean'],
					enum: ['marketplace', false]
				},
				{
					type: 'string'
				}
			]
		},
J
Joao Moreno 已提交
287
		extensionDependencies: {
288
			description: nls.localize('vscode.extension.extensionDependencies', 'Dependencies to other extensions. The identifier of an extension is always ${publisher}.${name}. For example: vscode.csharp.'),
J
Joao Moreno 已提交
289
			type: 'array',
290
			uniqueItems: true,
J
Joao Moreno 已提交
291
			items: {
S
Sandeep Somavarapu 已提交
292 293
				type: 'string',
				pattern: EXTENSION_IDENTIFIER_PATTERN
J
Joao Moreno 已提交
294 295
			}
		},
296 297 298 299 300 301 302 303 304
		extensionPack: {
			description: nls.localize('vscode.extension.contributes.extensionPack', "A set of extensions that can be installed together. The identifier of an extension is always ${publisher}.${name}. For example: vscode.csharp."),
			type: 'array',
			uniqueItems: true,
			items: {
				type: 'string',
				pattern: EXTENSION_IDENTIFIER_PATTERN
			}
		},
J
Joao Moreno 已提交
305 306 307 308 309 310
		scripts: {
			type: 'object',
			properties: {
				'vscode:prepublish': {
					description: nls.localize('vscode.extension.scripts.prepublish', 'Script executed before the package is published as a VS Code extension.'),
					type: 'string'
S
Sandeep Somavarapu 已提交
311 312
				},
				'vscode:uninstall': {
313
					description: nls.localize('vscode.extension.scripts.uninstall', 'Uninstall hook for VS Code extension. Script that gets executed when the extension is completely uninstalled from VS Code which is when VS Code is restarted (shutdown and start) after the extension is uninstalled. Only Node scripts are supported.'),
S
Sandeep Somavarapu 已提交
314
					type: 'string'
J
Joao Moreno 已提交
315 316
				}
			}
D
Denis Malinochkin 已提交
317 318 319 320
		},
		icon: {
			type: 'string',
			description: nls.localize('vscode.extension.icon', 'The path to a 128x128 pixel icon.')
J
Joao Moreno 已提交
321 322 323 324
		}
	}
};

325
export class ExtensionsRegistryImpl {
E
Erich Gamma 已提交
326 327 328 329 330 331 332

	private _extensionPoints: { [extPoint: string]: ExtensionPoint<any>; };

	constructor() {
		this._extensionPoints = {};
	}

A
Alex Dima 已提交
333
	public registerExtensionPoint<T>(extensionPoint: string, deps: IExtensionPoint<any>[], jsonSchema: IJSONSchema): IExtensionPoint<T> {
E
Erich Gamma 已提交
334 335 336
		if (hasOwnProperty.call(this._extensionPoints, extensionPoint)) {
			throw new Error('Duplicate extension point: ' + extensionPoint);
		}
A
Alex Dima 已提交
337
		let result = new ExtensionPoint<T>(extensionPoint);
E
Erich Gamma 已提交
338 339
		this._extensionPoints[extensionPoint] = result;

340
		schema.properties['contributes'].properties[extensionPoint] = jsonSchema;
E
Erich Gamma 已提交
341 342 343 344 345
		schemaRegistry.registerSchema(schemaId, schema);

		return result;
	}

A
Alex Dima 已提交
346 347
	public getExtensionPoints(): ExtensionPoint<any>[] {
		return Object.keys(this._extensionPoints).map(point => this._extensionPoints[point]);
E
Erich Gamma 已提交
348 349 350
	}
}

A
Alex Dima 已提交
351
const PRExtensions = {
352
	ExtensionsRegistry: 'ExtensionsRegistry'
E
Erich Gamma 已提交
353
};
A
Alex Dima 已提交
354
Registry.add(PRExtensions.ExtensionsRegistry, new ExtensionsRegistryImpl());
355
export const ExtensionsRegistry: ExtensionsRegistryImpl = Registry.as(PRExtensions.ExtensionsRegistry);
E
Erich Gamma 已提交
356

W
Wade Anderson 已提交
357
schemaRegistry.registerSchema(schemaId, schema);