tokenClassificationRegistry.ts 17.1 KB
Newer Older
M
Martin Aeschlimann 已提交
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as platform from 'vs/platform/registry/common/platform';
import { Color } from 'vs/base/common/color';
import { ITheme } from 'vs/platform/theme/common/themeService';
import * as nls from 'vs/nls';
10 11 12 13
import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { RunOnceScheduler } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
M
Martin Aeschlimann 已提交
14 15 16 17 18 19

export const TOKEN_TYPE_WILDCARD = '*';

// qualified string [type|*](.modifier)*
export type TokenClassificationString = string;

20
export const typeAndModifierIdPattern = '^\\w+[-_\\w+]*$';
M
Martin Aeschlimann 已提交
21
export const fontStylePattern = '^(\\s*(-?italic|-?bold|-?underline))*\\s*$';
22

23
export interface TokenSelector {
24
	match(type: string, modifiers: string[]): number;
M
Martin Aeschlimann 已提交
25
	readonly selectorString: string;
26 27
}

M
Martin Aeschlimann 已提交
28 29 30
export interface TokenTypeOrModifierContribution {
	readonly num: number;
	readonly id: string;
M
Martin Aeschlimann 已提交
31
	readonly superType?: string;
M
Martin Aeschlimann 已提交
32
	readonly description: string;
M
Martin Aeschlimann 已提交
33
	readonly deprecationMessage?: string;
M
Martin Aeschlimann 已提交
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
}


export interface TokenStyleData {
	foreground?: Color;
	bold?: boolean;
	underline?: boolean;
	italic?: boolean;
}

export class TokenStyle implements Readonly<TokenStyleData> {
	constructor(
		public readonly foreground?: Color,
		public readonly bold?: boolean,
		public readonly underline?: boolean,
		public readonly italic?: boolean,
	) {
	}
}

export namespace TokenStyle {
	export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }) {
		return new TokenStyle(data.foreground, data.bold, data.underline, data.italic);
	}
M
Martin Aeschlimann 已提交
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
	export function fromSettings(foreground: string | undefined, fontStyle: string | undefined): TokenStyle {
		let foregroundColor = undefined;
		if (foreground !== undefined) {
			foregroundColor = Color.fromHex(foreground);
		}
		let bold, underline, italic;
		if (fontStyle !== undefined) {
			fontStyle = fontStyle.trim();
			if (fontStyle.length === 0) {
				bold = italic = underline = false;
			} else {
				const expression = /-?italic|-?bold|-?underline/g;
				let match;
				while ((match = expression.exec(fontStyle))) {
					switch (match[0]) {
						case 'bold': bold = true; break;
						case 'italic': italic = true; break;
						case 'underline': underline = true; break;
					}
				}
			}
		}
		return new TokenStyle(foregroundColor, bold, underline, italic);

	}
M
Martin Aeschlimann 已提交
83 84 85 86 87 88 89 90 91
}

export type ProbeScope = string[];

export interface TokenStyleFunction {
	(theme: ITheme): TokenStyle | undefined;
}

export interface TokenStyleDefaults {
M
Martin Aeschlimann 已提交
92 93 94 95
	scopesToProbe?: ProbeScope[];
	light?: TokenStyleValue;
	dark?: TokenStyleValue;
	hc?: TokenStyleValue;
M
Martin Aeschlimann 已提交
96 97 98
}

export interface TokenStylingDefaultRule {
99
	selector: TokenSelector;
M
Martin Aeschlimann 已提交
100 101 102 103
	defaults: TokenStyleDefaults;
}

export interface TokenStylingRule {
104 105
	style: TokenStyle;
	selector: TokenSelector;
M
Martin Aeschlimann 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118 119
}

/**
 * A TokenStyle Value is either a token style literal, or a TokenClassificationString
 */
export type TokenStyleValue = TokenStyle | TokenClassificationString;

// TokenStyle registry
export const Extensions = {
	TokenClassificationContribution: 'base.contributions.tokenClassification'
};

export interface ITokenClassificationRegistry {

120 121
	readonly onDidChangeSchema: Event<void>;

M
Martin Aeschlimann 已提交
122 123 124
	/**
	 * Register a token type to the registry.
	 * @param id The TokenType id as used in theme description files
125
	 * @param description the description
M
Martin Aeschlimann 已提交
126
	 */
M
Martin Aeschlimann 已提交
127
	registerTokenType(id: string, description: string, superType?: string, deprecationMessage?: string): void;
M
Martin Aeschlimann 已提交
128 129 130 131

	/**
	 * Register a token modifier to the registry.
	 * @param id The TokenModifier id as used in theme description files
132
	 * @param description the description
M
Martin Aeschlimann 已提交
133 134 135
	 */
	registerTokenModifier(id: string, description: string): void;

136 137 138 139 140 141 142
	/**
	 * Parses a token selector from a selector string.
	 * @param selectorString selector string in the form (*|type)(.modifier)*
	 * @returns the parsesd selector
	 * @throws an error if the string is not a valid selector
	 */
	parseTokenSelector(selectorString: string): TokenSelector;
M
Martin Aeschlimann 已提交
143

M
Martin Aeschlimann 已提交
144 145 146 147 148
	/**
	 * Register a TokenStyle default to the registry.
	 * @param selector The rule selector
	 * @param defaults The default values
	 */
149
	registerTokenStyleDefault(selector: TokenSelector, defaults: TokenStyleDefaults): void;
M
Martin Aeschlimann 已提交
150

151 152 153 154
	/**
	 * Deregister a TokenStyle default to the registry.
	 * @param selector The rule selector
	 */
155
	deregisterTokenStyleDefault(selector: TokenSelector): void;
156

M
Martin Aeschlimann 已提交
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
	/**
	 * Deregister a TokenType from the registry.
	 */
	deregisterTokenType(id: string): void;

	/**
	 * Deregister a TokenModifier from the registry.
	 */
	deregisterTokenModifier(id: string): void;

	/**
	 * Get all TokenType contributions
	 */
	getTokenTypes(): TokenTypeOrModifierContribution[];

	/**
	 * Get all TokenModifier contributions
	 */
	getTokenModifiers(): TokenTypeOrModifierContribution[];

	/**
178
	 * The styling rules to used when a schema does not define any styling rules.
M
Martin Aeschlimann 已提交
179
	 */
180
	getTokenStylingDefaultRules(): TokenStylingDefaultRule[];
M
Martin Aeschlimann 已提交
181

182 183 184 185 186
	/**
	 * JSON schema for an object to assign styling to token classifications
	 */
	getTokenStylingSchema(): IJSONSchema;
}
M
Martin Aeschlimann 已提交
187 188 189

class TokenClassificationRegistry implements ITokenClassificationRegistry {

190 191 192
	private readonly _onDidChangeSchema = new Emitter<void>();
	readonly onDidChangeSchema: Event<void> = this._onDidChangeSchema.event;

M
Martin Aeschlimann 已提交
193 194 195 196 197 198 199 200
	private currentTypeNumber = 0;
	private currentModifierBit = 1;

	private tokenTypeById: { [key: string]: TokenTypeOrModifierContribution };
	private tokenModifierById: { [key: string]: TokenTypeOrModifierContribution };

	private tokenStylingDefaultRules: TokenStylingDefaultRule[] = [];

M
Martin Aeschlimann 已提交
201 202
	private typeHierarchy: { [id: string]: string[] };

203 204 205
	private tokenStylingSchema: IJSONSchema & { properties: IJSONSchemaMap } = {
		type: 'object',
		properties: {},
206
		additionalProperties: getStylingSchemeEntry(),
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
		definitions: {
			style: {
				type: 'object',
				description: nls.localize('schema.token.settings', 'Colors and styles for the token.'),
				properties: {
					foreground: {
						type: 'string',
						description: nls.localize('schema.token.foreground', 'Foreground color for the token.'),
						format: 'color-hex',
						default: '#ff0000'
					},
					background: {
						type: 'string',
						deprecationMessage: nls.localize('schema.token.background.warning', 'Token background colors are currently not supported.')
					},
					fontStyle: {
						type: 'string',
224
						description: nls.localize('schema.token.fontStyle', 'Font style of the rule: \'italic\', \'bold\' or \'underline\' or a combination. The empty string unsets inherited settings.'),
M
Martin Aeschlimann 已提交
225
						pattern: fontStylePattern,
226 227
						patternErrorMessage: nls.localize('schema.fontStyle.error', 'Font style must be \'italic\', \'bold\' or \'underline\' or a combination. The empty string unsets all styles.'),
						defaultSnippets: [{ label: nls.localize('schema.token.fontStyle.none', 'None (clear inherited style)'), bodyText: '""' }, { body: 'italic' }, { body: 'bold' }, { body: 'underline' }, { body: 'italic underline' }, { body: 'bold underline' }, { body: 'italic bold underline' }]
228 229 230 231 232 233 234 235
					}
				},
				additionalProperties: false,
				defaultSnippets: [{ body: { foreground: '${1:#FF0000}', fontStyle: '${2:bold}' } }]
			}
		}
	};

M
Martin Aeschlimann 已提交
236 237 238
	constructor() {
		this.tokenTypeById = {};
		this.tokenModifierById = {};
M
Martin Aeschlimann 已提交
239
		this.typeHierarchy = {};
M
Martin Aeschlimann 已提交
240 241
	}

M
Martin Aeschlimann 已提交
242
	public registerTokenType(id: string, description: string, superType?: string, deprecationMessage?: string): void {
243 244 245
		if (!id.match(typeAndModifierIdPattern)) {
			throw new Error('Invalid token type id.');
		}
M
Martin Aeschlimann 已提交
246 247 248
		if (superType && !superType.match(typeAndModifierIdPattern)) {
			throw new Error('Invalid token super type id.');
		}
249

M
Martin Aeschlimann 已提交
250
		const num = this.currentTypeNumber++;
M
Martin Aeschlimann 已提交
251
		let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, superType, description, deprecationMessage };
M
Martin Aeschlimann 已提交
252
		this.tokenTypeById[id] = tokenStyleContribution;
253 254

		this.tokenStylingSchema.properties[id] = getStylingSchemeEntry(description, deprecationMessage);
M
Martin Aeschlimann 已提交
255
		this.typeHierarchy = {};
M
Martin Aeschlimann 已提交
256 257 258
	}

	public registerTokenModifier(id: string, description: string, deprecationMessage?: string): void {
259 260 261 262
		if (!id.match(typeAndModifierIdPattern)) {
			throw new Error('Invalid token modifier id.');
		}

M
Martin Aeschlimann 已提交
263 264 265 266
		const num = this.currentModifierBit;
		this.currentModifierBit = this.currentModifierBit * 2;
		let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage };
		this.tokenModifierById[id] = tokenStyleContribution;
267 268

		this.tokenStylingSchema.properties[`*.${id}`] = getStylingSchemeEntry(description, deprecationMessage);
M
Martin Aeschlimann 已提交
269 270
	}

271
	public parseTokenSelector(selectorString: string): TokenSelector {
272
		const [selectorType, ...selectorModifiers] = selectorString.split('.');
273

274
		if (!selectorType) {
275 276
			return {
				match: () => -1,
M
Martin Aeschlimann 已提交
277
				selectorString
278 279
			};
		}
M
Martin Aeschlimann 已提交
280

281
		return {
282
			match: (type: string, modifiers: string[]) => {
M
Martin Aeschlimann 已提交
283 284 285 286 287 288 289 290
				let score = 0;
				if (selectorType !== TOKEN_TYPE_WILDCARD) {
					const hierarchy = this.getTypeHierarchy(type);
					const level = hierarchy.indexOf(selectorType);
					if (level === -1) {
						return -1;
					}
					score = 100 - level;
291
				}
292 293 294 295 296
				// all selector modifiers must be present
				for (const selectorModifier of selectorModifiers) {
					if (modifiers.indexOf(selectorModifier) === -1) {
						return -1;
					}
297
				}
M
Martin Aeschlimann 已提交
298
				return score + selectorModifiers.length * 100;
299
			},
M
Martin Aeschlimann 已提交
300
			selectorString
301 302 303
		};
	}

304 305
	public registerTokenStyleDefault(selector: TokenSelector, defaults: TokenStyleDefaults): void {
		this.tokenStylingDefaultRules.push({ selector, defaults });
M
Martin Aeschlimann 已提交
306 307
	}

308
	public deregisterTokenStyleDefault(selector: TokenSelector): void {
M
Martin Aeschlimann 已提交
309 310
		const selectorString = selector.selectorString;
		this.tokenStylingDefaultRules = this.tokenStylingDefaultRules.filter(r => r.selector.selectorString !== selectorString);
311 312
	}

M
Martin Aeschlimann 已提交
313 314
	public deregisterTokenType(id: string): void {
		delete this.tokenTypeById[id];
315
		delete this.tokenStylingSchema.properties[id];
M
Martin Aeschlimann 已提交
316
		this.typeHierarchy = {};
M
Martin Aeschlimann 已提交
317 318 319 320
	}

	public deregisterTokenModifier(id: string): void {
		delete this.tokenModifierById[id];
321
		delete this.tokenStylingSchema.properties[`*.${id}`];
M
Martin Aeschlimann 已提交
322 323 324 325 326 327 328 329 330 331
	}

	public getTokenTypes(): TokenTypeOrModifierContribution[] {
		return Object.keys(this.tokenTypeById).map(id => this.tokenTypeById[id]);
	}

	public getTokenModifiers(): TokenTypeOrModifierContribution[] {
		return Object.keys(this.tokenModifierById).map(id => this.tokenModifierById[id]);
	}

332 333 334 335
	public getTokenStylingSchema(): IJSONSchema {
		return this.tokenStylingSchema;
	}

336 337 338
	public getTokenStylingDefaultRules(): TokenStylingDefaultRule[] {
		return this.tokenStylingDefaultRules;
	}
M
Martin Aeschlimann 已提交
339

M
Martin Aeschlimann 已提交
340 341 342 343 344 345 346 347 348 349 350 351 352
	private getTypeHierarchy(typeId: string): string[] {
		let hierarchy = this.typeHierarchy[typeId];
		if (!hierarchy) {
			this.typeHierarchy[typeId] = hierarchy = [typeId];
			let type = this.tokenTypeById[typeId];
			while (type && type.superType) {
				hierarchy.push(type.superType);
				type = this.tokenTypeById[type.superType];
			}
		}
		return hierarchy;
	}

353

M
Martin Aeschlimann 已提交
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
	public toString() {
		let sorter = (a: string, b: string) => {
			let cat1 = a.indexOf('.') === -1 ? 0 : 1;
			let cat2 = b.indexOf('.') === -1 ? 0 : 1;
			if (cat1 !== cat2) {
				return cat1 - cat2;
			}
			return a.localeCompare(b);
		};

		return Object.keys(this.tokenTypeById).sort(sorter).map(k => `- \`${k}\`: ${this.tokenTypeById[k].description}`).join('\n');
	}

}


const tokenClassificationRegistry = new TokenClassificationRegistry();
platform.Registry.add(Extensions.TokenClassificationContribution, tokenClassificationRegistry);

M
Martin Aeschlimann 已提交
373 374 375
registerDefaultClassifications();

function registerDefaultClassifications(): void {
M
Martin Aeschlimann 已提交
376 377 378 379
	function registerTokenType(id: string, description: string, scopesToProbe: ProbeScope[] = [], superType?: string, deprecationMessage?: string): string {
		tokenClassificationRegistry.registerTokenType(id, description, superType, deprecationMessage);
		if (scopesToProbe) {
			registerTokenStyleDefault(id, scopesToProbe);
M
Martin Aeschlimann 已提交
380 381
		}
		return id;
M
Martin Aeschlimann 已提交
382 383
	}

M
Martin Aeschlimann 已提交
384 385 386 387 388 389 390 391 392
	function registerTokenStyleDefault(selectorString: string, scopesToProbe: ProbeScope[]) {
		try {
			const selector = tokenClassificationRegistry.parseTokenSelector(selectorString);
			tokenClassificationRegistry.registerTokenStyleDefault(selector, { scopesToProbe });
		} catch (e) {
			console.log(e);
		}
	}

M
Martin Aeschlimann 已提交
393 394 395 396 397 398 399 400 401 402 403
	// default token types

	registerTokenType('comment', nls.localize('comment', "Style for comments."), [['comment']]);
	registerTokenType('string', nls.localize('string', "Style for strings."), [['string']]);
	registerTokenType('keyword', nls.localize('keyword', "Style for keywords."), [['keyword.control']]);
	registerTokenType('number', nls.localize('number', "Style for numbers."), [['constant.numeric']]);
	registerTokenType('regexp', nls.localize('regexp', "Style for expressions."), [['constant.regexp']]);
	registerTokenType('operator', nls.localize('operator', "Style for operators."), [['keyword.operator']]);

	registerTokenType('namespace', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]);

404
	registerTokenType('type', nls.localize('type', "Style for types."), [['entity.name.type'], ['support.type'], ['support.class']]);
M
Martin Aeschlimann 已提交
405
	registerTokenType('struct', nls.localize('struct', "Style for structs."), [['storage.type.struct']], 'type');
406 407 408
	registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.type.class']], 'type');
	registerTokenType('interface', nls.localize('interface', "Style for interfaces."), [['entity.name.type.interface']], 'type');
	registerTokenType('enum', nls.localize('enum', "Style for enums."), [['entity.name.type.enum']], 'type');
409
	registerTokenType('typeParameter', nls.localize('typeParameter', "Style for type parameters."), [['entity.name.type', 'meta.type.parameters']], 'type');
M
Martin Aeschlimann 已提交
410 411

	registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]);
412 413
	registerTokenType('member', nls.localize('member', "Style for member"), [['entity.name.function.member'], ['support.function']]);
	registerTokenType('macro', nls.localize('macro', "Style for macros."), [['entity.name.other.preprocessor.macro']], 'function');
M
Martin Aeschlimann 已提交
414

415
	registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable.other.readwrite'], ['entity.name.variable']]);
416 417
	registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), [['variable.parameter']], 'variable');
	registerTokenType('property', nls.localize('property', "Style for properties."), [['variable.other.property']], 'variable');
418 419
	registerTokenType('enumMember', nls.localize('enumMember', "Style for enum members."), [['variable.other.enummember']], 'variable');
	registerTokenType('event', nls.localize('event', "Style for events."), [['variable.other.event']], 'variable');
M
Martin Aeschlimann 已提交
420 421 422 423 424 425 426 427 428 429 430 431

	registerTokenType('label', nls.localize('labels', "Style for labels. "), undefined);

	// default token modifiers

	tokenClassificationRegistry.registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined);
	tokenClassificationRegistry.registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined);
	tokenClassificationRegistry.registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined);
	tokenClassificationRegistry.registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined);
	tokenClassificationRegistry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined);
	tokenClassificationRegistry.registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined);
	tokenClassificationRegistry.registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined);
432
	tokenClassificationRegistry.registerTokenModifier('readonly', nls.localize('readonly', "Style to use for symbols that are readonly."), undefined);
433 434


435
	registerTokenStyleDefault('variable.readonly', [['variable.other.constant']]);
M
Martin Aeschlimann 已提交
436 437
}

M
Martin Aeschlimann 已提交
438 439 440 441
export function getTokenClassificationRegistry(): ITokenClassificationRegistry {
	return tokenClassificationRegistry;
}

442
function getStylingSchemeEntry(description?: string, deprecationMessage?: string): IJSONSchema {
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
	return {
		description,
		deprecationMessage,
		defaultSnippets: [{ body: '${1:#ff0000}' }],
		anyOf: [
			{
				type: 'string',
				format: 'color-hex'
			},
			{
				$ref: '#definitions/style'
			}
		]
	};
}

export const tokenStylingSchemaId = 'vscode://schemas/token-styling';

let schemaRegistry = platform.Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
schemaRegistry.registerSchema(tokenStylingSchemaId, tokenClassificationRegistry.getTokenStylingSchema());

const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(tokenStylingSchemaId), 200);
tokenClassificationRegistry.onDidChangeSchema(() => {
	if (!delayer.isScheduled()) {
		delayer.schedule();
	}
});