tokenClassificationExtensionPoint.ts 10.6 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry';
M
Martin Aeschlimann 已提交
8
import { getTokenClassificationRegistry, ITokenClassificationRegistry, typeAndModifierIdPattern, TokenStyleDefaults, TokenStyle, fontStylePattern } from 'vs/platform/theme/common/tokenClassificationRegistry';
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
import { textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema';

interface ITokenTypeExtensionPoint {
	id: string;
	description: string;
}

interface ITokenModifierExtensionPoint {
	id: string;
	description: string;
}

interface ITokenStyleDefaultExtensionPoint {
	selector: string;
	scopes?: string[];
	light?: {
		foreground?: string;
		fontStyle?: string;
	};
	dark?: {
		foreground?: string;
		fontStyle?: string;
	};
	highContrast?: {
		foreground?: string;
		fontStyle?: string;
	};
}

M
Martin Aeschlimann 已提交
38
const selectorPattern = '^([-_\\w]+|\\*)(\\.[-_\\w+]+)*$';
M
Martin Aeschlimann 已提交
39 40
const colorPattern = '^#([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$';

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
const tokenClassificationRegistry: ITokenClassificationRegistry = getTokenClassificationRegistry();

const tokenTypeExtPoint = ExtensionsRegistry.registerExtensionPoint<ITokenTypeExtensionPoint[]>({
	extensionPoint: 'tokenTypes',
	jsonSchema: {
		description: nls.localize('contributes.tokenTypes', 'Contributes semantic token types.'),
		type: 'array',
		items: {
			type: 'object',
			properties: {
				id: {
					type: 'string',
					description: nls.localize('contributes.tokenTypes.id', 'The identifier of the token type'),
					pattern: typeAndModifierIdPattern,
					patternErrorMessage: nls.localize('contributes.tokenTypes.id.format', 'Identifiers should be in the form letterOrDigit[_-letterOrDigit]*'),
				},
				description: {
					type: 'string',
					description: nls.localize('contributes.color.description', 'The description of the token type'),
				}
			}
		}
	}
});

const tokenModifierExtPoint = ExtensionsRegistry.registerExtensionPoint<ITokenModifierExtensionPoint[]>({
	extensionPoint: 'tokenModifiers',
	jsonSchema: {
		description: nls.localize('contributes.tokenModifiers', 'Contributes semantic token modifiers.'),
		type: 'array',
		items: {
			type: 'object',
			properties: {
				id: {
					type: 'string',
					description: nls.localize('contributes.tokenModifiers.id', 'The identifier of the token modifier'),
					pattern: typeAndModifierIdPattern,
M
Martin Aeschlimann 已提交
78
					patternErrorMessage: nls.localize('contributes.tokenModifiers.id.format', 'Identifiers should be in the form letterOrDigit[_-letterOrDigit]*')
79 80
				},
				description: {
M
Martin Aeschlimann 已提交
81
					description: nls.localize('contributes.tokenModifiers.description', 'The description of the token modifier')
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
				}
			}
		}
	}
});

const tokenStyleDefaultsExtPoint = ExtensionsRegistry.registerExtensionPoint<ITokenStyleDefaultExtensionPoint[]>({
	extensionPoint: 'tokenStyleDefaults',
	jsonSchema: {
		description: nls.localize('contributes.tokenStyleDefaults', 'Contributes semantic token style default.'),
		type: 'array',
		items: {
			type: 'object',
			properties: {
				selector: {
					type: 'string',
					description: nls.localize('contributes.tokenStyleDefaults.selector', 'The selector matching token types and modifiers.'),
M
Martin Aeschlimann 已提交
99
					pattern: selectorPattern,
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
					patternErrorMessage: nls.localize('contributes.tokenStyleDefaults.selector.format', 'Selectors should be in the form (type|*)(.modifier)*'),
				},
				scopes: {
					type: 'array',
					description: nls.localize('contributes.scopes.light', 'A list of textmate scopes that are matched against the current color theme to find a default style'),
					items: {
						type: 'string'
					}
				},
				light: {
					description: nls.localize('contributes.tokenStyleDefaults.light', 'The default style used for light themes'),
					$ref: textmateColorSettingsSchemaId
				},
				dark: {
					description: nls.localize('contributes.tokenStyleDefaults.dark', 'The default style used for dark themes'),
					$ref: textmateColorSettingsSchemaId
				},
				highContrast: {
M
Martin Aeschlimann 已提交
118
					description: nls.localize('contributes.tokenStyleDefaults.hc', 'The default style used for high contrast themes'),
119 120 121 122 123 124 125 126 127 128 129
					$ref: textmateColorSettingsSchemaId
				}
			}
		}
	}
});


export class TokenClassificationExtensionPoints {

	constructor() {
M
Martin Aeschlimann 已提交
130
		function validateTypeOrModifier(contribution: ITokenTypeExtensionPoint | ITokenModifierExtensionPoint, extensionPoint: string, collector: ExtensionMessageCollector): boolean {
131 132 133 134 135
			if (typeof contribution.id !== 'string' || contribution.id.length === 0) {
				collector.error(nls.localize('invalid.id', "'configuration.{0}.id' must be defined and can not be empty", extensionPoint));
				return false;
			}
			if (!contribution.id.match(typeAndModifierIdPattern)) {
M
Martin Aeschlimann 已提交
136
				collector.error(nls.localize('invalid.id.format', "'configuration.{0}.id' must follow the pattern letterOrDigit[-_letterOrDigit]*", extensionPoint));
137 138 139 140 141 142 143 144
				return false;
			}
			if (typeof contribution.description !== 'string' || contribution.id.length === 0) {
				collector.error(nls.localize('invalid.description', "'configuration.{0}.description' must be defined and can not be empty", extensionPoint));
				return false;
			}
			return true;
		}
M
Martin Aeschlimann 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
		function validateStyle(style: { foreground?: string; fontStyle?: string; } | undefined, extensionPoint: string, collector: ExtensionMessageCollector): TokenStyle | undefined {
			if (!style) {
				return undefined;
			}
			if (style.foreground) {
				if (typeof style.foreground !== 'string' || !style.foreground.match(colorPattern)) {
					collector.error(nls.localize('invalid.color', "'configuration.{0}.foreground'  must follow the pattern #RRGGBB[AA]", extensionPoint));
					return undefined;
				}
			}
			if (style.fontStyle) {
				if (typeof style.fontStyle !== 'string' || !style.fontStyle.match(fontStylePattern)) {
					collector.error(nls.localize('invalid.fontStyle', "'configuration.{0}.fontStyle'  must be a one or a compination of  \'italic\', \'bold\' or \'underline\' or the empty string", extensionPoint));
					return undefined;
				}
			}
			return TokenStyle.fromSettings(style.foreground, style.fontStyle);
		}
163 164 165 166 167 168 169 170 171 172 173

		tokenTypeExtPoint.setHandler((extensions, delta) => {
			for (const extension of delta.added) {
				const extensionValue = <ITokenTypeExtensionPoint[]>extension.value;
				const collector = extension.collector;

				if (!extensionValue || !Array.isArray(extensionValue)) {
					collector.error(nls.localize('invalid.tokenTypeConfiguration', "'configuration.tokenType' must be a array"));
					return;
				}
				for (const contribution of extensionValue) {
M
Martin Aeschlimann 已提交
174
					if (validateTypeOrModifier(contribution, 'tokenType', collector)) {
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
						tokenClassificationRegistry.registerTokenType(contribution.id, contribution.description);
					}
				}
			}
			for (const extension of delta.removed) {
				const extensionValue = <ITokenTypeExtensionPoint[]>extension.value;
				for (const contribution of extensionValue) {
					tokenClassificationRegistry.deregisterTokenType(contribution.id);
				}
			}
		});
		tokenModifierExtPoint.setHandler((extensions, delta) => {
			for (const extension of delta.added) {
				const extensionValue = <ITokenModifierExtensionPoint[]>extension.value;
				const collector = extension.collector;

				if (!extensionValue || !Array.isArray(extensionValue)) {
					collector.error(nls.localize('invalid.tokenModifierConfiguration', "'configuration.tokenModifier' must be a array"));
					return;
				}
				for (const contribution of extensionValue) {
M
Martin Aeschlimann 已提交
196
					if (validateTypeOrModifier(contribution, 'tokenModifier', collector)) {
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
						tokenClassificationRegistry.registerTokenModifier(contribution.id, contribution.description);
					}
				}
			}
			for (const extension of delta.removed) {
				const extensionValue = <ITokenModifierExtensionPoint[]>extension.value;
				for (const contribution of extensionValue) {
					tokenClassificationRegistry.deregisterTokenModifier(contribution.id);
				}
			}
		});
		tokenStyleDefaultsExtPoint.setHandler((extensions, delta) => {
			for (const extension of delta.added) {
				const extensionValue = <ITokenStyleDefaultExtensionPoint[]>extension.value;
				const collector = extension.collector;

				if (!extensionValue || !Array.isArray(extensionValue)) {
					collector.error(nls.localize('invalid.tokenStyleDefaultConfiguration', "'configuration.tokenStyleDefaults' must be a array"));
					return;
				}
				for (const contribution of extensionValue) {
					if (typeof contribution.selector !== 'string' || contribution.selector.length === 0) {
						collector.error(nls.localize('invalid.selector', "'configuration.tokenStyleDefaults.selector' must be defined and can not be empty"));
M
Martin Aeschlimann 已提交
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
						continue;
					}
					if (!contribution.selector.match(selectorPattern)) {
						collector.error(nls.localize('invalid.selector.format', "'configuration.tokenStyleDefaults.selector' must be in the form (type|*)(.modifier)*"));
						continue;
					}

					const tokenStyleDefault: TokenStyleDefaults = {};

					if (contribution.scopes) {
						if ((!Array.isArray(contribution.scopes) || contribution.scopes.some(s => typeof s !== 'string'))) {
							collector.error(nls.localize('invalid.scopes', "If defined, 'configuration.tokenStyleDefaults.scopes' must must be an array or strings"));
							continue;
						}
						tokenStyleDefault.scopesToProbe = [contribution.scopes];
					}
					tokenStyleDefault.light = validateStyle(contribution.light, 'tokenStyleDefaults.light', collector);
					tokenStyleDefault.dark = validateStyle(contribution.dark, 'tokenStyleDefaults.dark', collector);
					tokenStyleDefault.hc = validateStyle(contribution.highContrast, 'tokenStyleDefaults.highContrast', collector);

					const [type, ...modifiers] = contribution.selector.split('.');
					const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers);
					if (classification) {
						tokenClassificationRegistry.registerTokenStyleDefault(classification, tokenStyleDefault);
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
					}
				}
			}
			for (const extension of delta.removed) {
				const extensionValue = <ITokenStyleDefaultExtensionPoint[]>extension.value;
				for (const contribution of extensionValue) {
					const [type, ...modifiers] = contribution.selector.split('.');
					const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers);
					if (classification) {
						tokenClassificationRegistry.deregisterTokenStyleDefault(classification);
					}
				}
			}
		});
	}
}