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

6
import { basename } from 'vs/base/common/path';
7
import * as Json from 'vs/base/common/json';
M
Martin Aeschlimann 已提交
8
import { Color } from 'vs/base/common/color';
9
import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IWorkbenchColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, ISemanticTokenRules, ISemanticTokenColorizationSetting, ISemanticTokenColorCustomizations, IExperimentalSemanticTokenColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService';
10
import { convertSettings } from 'vs/workbench/services/themes/common/themeCompatibility';
11
import * as nls from 'vs/nls';
12
import * as types from 'vs/base/common/types';
13
import * as objects from 'vs/base/common/objects';
14
import * as arrays from 'vs/base/common/arrays';
A
Alex Dima 已提交
15
import * as resources from 'vs/base/common/resources';
M
Martin Aeschlimann 已提交
16
import { Extensions as ColorRegistryExtensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
17
import { ITokenStyle, getThemeTypeSelector } from 'vs/platform/theme/common/themeService';
18
import { Registry } from 'vs/platform/registry/common/platform';
19
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
20
import { URI } from 'vs/base/common/uri';
21
import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser';
22
import { TokenStyle, SemanticTokenRule, ProbeScope, getTokenClassificationRegistry, TokenStyleValue, TokenStyleData, parseClassifierString } from 'vs/platform/theme/common/tokenClassificationRegistry';
M
Martin Aeschlimann 已提交
23
import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher';
24
import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader';
25
import { CharCode } from 'vs/base/common/charCode';
M
Martin Aeschlimann 已提交
26
import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage';
27
import { ThemeConfiguration } from 'vs/workbench/services/themes/common/themeConfiguration';
28
import { ColorScheme } from 'vs/platform/theme/common/theme';
29

M
Martin Aeschlimann 已提交
30 31
let colorRegistry = Registry.as<IColorRegistry>(ColorRegistryExtensions.ColorContribution);

M
Martin Aeschlimann 已提交
32
let tokenClassificationRegistry = getTokenClassificationRegistry();
33

34
const tokenGroupToScopesMap = {
35
	comments: ['comment', 'punctuation.definition.comment'],
A
Alex Ross 已提交
36
	strings: ['string', 'meta.embedded.assembly'],
37
	keywords: ['keyword - keyword.operator', 'keyword.control', 'storage', 'storage.type'],
38
	numbers: ['constant.numeric'],
39
	types: ['entity.name.type', 'entity.name.class', 'support.type', 'support.class'],
40
	functions: ['entity.name.function', 'support.function'],
41
	variables: ['variable', 'entity.name.variable']
42 43
};

M
Martin Aeschlimann 已提交
44

45
export type TokenStyleDefinition = SemanticTokenRule | ProbeScope[] | TokenStyleValue;
46 47
export type TokenStyleDefinitions = { [P in keyof TokenStyleData]?: TokenStyleDefinition | undefined };

48 49
export type TextMateThemingRuleDefinitions = { [P in keyof TokenStyleData]?: ITextMateThemingRule | undefined; } & { scope?: ProbeScope; };

M
Martin Aeschlimann 已提交
50 51
const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData';

M
Martin Aeschlimann 已提交
52
export class ColorThemeData implements IWorkbenchColorTheme {
M
Martin Aeschlimann 已提交
53 54 55 56 57

	id: string;
	label: string;
	settingsId: string;
	description?: string;
58
	isLoaded: boolean;
A
Alex Dima 已提交
59
	location?: URI;
60 61 62
	watch?: boolean;
	extensionData?: ExtensionData;

63 64
	private themeSemanticHighlighting: boolean | undefined;
	private customSemanticHighlighting: boolean | undefined;
65
	private customSemanticHighlightingDeprecated: boolean | undefined;
66

67 68
	private themeTokenColors: ITextMateThemingRule[] = [];
	private customTokenColors: ITextMateThemingRule[] = [];
69 70 71
	private colorMap: IColorMap = {};
	private customColorMap: IColorMap = {};

72 73
	private semanticTokenRules: SemanticTokenRule[] = [];
	private customSemanticTokenRules: SemanticTokenRule[] = [];
M
Martin Aeschlimann 已提交
74

M
more  
Martin Aeschlimann 已提交
75 76
	private themeTokenScopeMatchers: Matcher<ProbeScope>[] | undefined;
	private customTokenScopeMatchers: Matcher<ProbeScope>[] | undefined;
M
Martin Aeschlimann 已提交
77

78
	private textMateThemingRules: ITextMateThemingRule[] | undefined = undefined; // created on demand
79 80
	private tokenColorIndex: TokenColorIndex | undefined = undefined; // created on demand

81 82 83 84 85
	private constructor(id: string, label: string, settingsId: string) {
		this.id = id;
		this.label = label;
		this.settingsId = settingsId;
		this.isLoaded = false;
86 87 88
	}

	get semanticHighlighting(): boolean {
89 90 91 92 93 94 95
		if (this.customSemanticHighlighting !== undefined) {
			return this.customSemanticHighlighting;
		}
		if (this.customSemanticHighlightingDeprecated !== undefined) {
			return this.customSemanticHighlightingDeprecated;
		}
		return !!this.themeSemanticHighlighting;
96
	}
97

98
	get tokenColors(): ITextMateThemingRule[] {
99 100 101 102 103 104 105 106
		if (!this.textMateThemingRules) {
			const result: ITextMateThemingRule[] = [];

			// the default rule (scope empty) is always the first rule. Ignore all other default rules.
			const foreground = this.getColor(editorForeground) || this.getDefault(editorForeground)!;
			const background = this.getColor(editorBackground) || this.getDefault(editorBackground)!;
			result.push({
				settings: {
107 108
					foreground: normalizeColor(foreground),
					background: normalizeColor(background)
109 110
				}
			});
111

112
			let hasDefaultTokens = false;
113

114 115 116 117 118
			function addRule(rule: ITextMateThemingRule) {
				if (rule.scope && rule.settings) {
					if (rule.scope === 'token.info-token') {
						hasDefaultTokens = true;
					}
119
					result.push({ scope: rule.scope, settings: { foreground: normalizeColor(rule.settings.foreground), background: normalizeColor(rule.settings.background), fontStyle: rule.settings.fontStyle } });
120 121 122
				}
			}

123 124 125 126
			this.themeTokenColors.forEach(addRule);
			// Add the custom colors after the theme colors
			// so that they will override them
			this.customTokenColors.forEach(addRule);
127

128 129 130 131
			if (!hasDefaultTokens) {
				defaultThemeColors[this.type].forEach(addRule);
			}
			this.textMateThemingRules = result;
132
		}
133
		return this.textMateThemingRules;
134
	}
135

136 137
	public getColor(colorId: ColorIdentifier, useDefault?: boolean): Color | undefined {
		let color: Color | undefined = this.customColorMap[colorId];
138 139
		if (color) {
			return color;
140
		}
141
		color = this.colorMap[colorId];
142
		if (useDefault !== false && types.isUndefined(color)) {
143 144 145 146 147
			color = this.getDefault(colorId);
		}
		return color;
	}

M
Martin Aeschlimann 已提交
148
	private getTokenStyle(type: string, modifiers: string[], language: string, useDefault = true, definitions: TokenStyleDefinitions = {}): TokenStyle | undefined {
149 150 151 152 153 154 155 156 157 158 159 160 161
		let result: any = {
			foreground: undefined,
			bold: undefined,
			underline: undefined,
			italic: undefined
		};
		let score = {
			foreground: -1,
			bold: -1,
			underline: -1,
			italic: -1
		};

162
		function _processStyle(matchScore: number, style: TokenStyle, definition: TokenStyleDefinition) {
163 164 165
			if (style.foreground && score.foreground <= matchScore) {
				score.foreground = matchScore;
				result.foreground = style.foreground;
166
				definitions.foreground = definition;
167 168 169 170 171 172 173 174
			}
			for (let p of ['bold', 'underline', 'italic']) {
				const property = p as keyof TokenStyle;
				const info = style[property];
				if (info !== undefined) {
					if (score[property] <= matchScore) {
						score[property] = matchScore;
						result[property] = info;
175
						definitions[property] = definition;
176 177 178 179
					}
				}
			}
		}
180
		function _processSemanticTokenRule(rule: SemanticTokenRule) {
M
Martin Aeschlimann 已提交
181
			const matchScore = rule.selector.match(type, modifiers, language);
182
			if (matchScore >= 0) {
183
				_processStyle(matchScore, rule.style, rule);
184 185
			}
		}
186 187 188 189

		this.semanticTokenRules.forEach(_processSemanticTokenRule);
		this.customSemanticTokenRules.forEach(_processSemanticTokenRule);

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
		let hasUndefinedStyleProperty = false;
		for (let k in score) {
			const key = k as keyof TokenStyle;
			if (score[key] === -1) {
				hasUndefinedStyleProperty = true;
			} else {
				score[key] = Number.MAX_VALUE; // set it to the max, so it won't be replaced by a default
			}
		}
		if (hasUndefinedStyleProperty) {
			for (const rule of tokenClassificationRegistry.getTokenStylingDefaultRules()) {
				const matchScore = rule.selector.match(type, modifiers, language);
				if (matchScore >= 0) {
					let style: TokenStyle | undefined;
					if (rule.defaults.scopesToProbe) {
						style = this.resolveScopes(rule.defaults.scopesToProbe);
						if (style) {
							_processStyle(matchScore, style, rule.defaults.scopesToProbe);
						}
					}
					if (!style && useDefault !== false) {
						const tokenStyleValue = rule.defaults[this.type];
						style = this.resolveTokenStyleValue(tokenStyleValue);
						if (style) {
							_processStyle(matchScore, style, tokenStyleValue!);
						}
					}
				}
			}
		}
220 221 222 223 224 225 226
		return TokenStyle.fromData(result);

	}

	/**
	 * @param tokenStyleValue Resolve a tokenStyleValue in the context of a theme
	 */
M
Martin Aeschlimann 已提交
227
	public resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | undefined): TokenStyle | undefined {
M
Martin Aeschlimann 已提交
228
		if (tokenStyleValue === undefined) {
229 230
			return undefined;
		} else if (typeof tokenStyleValue === 'string') {
M
Martin Aeschlimann 已提交
231 232
			const { type, modifiers, language } = parseClassifierString(tokenStyleValue, '');
			return this.getTokenStyle(type, modifiers, language);
233 234 235 236
		} else if (typeof tokenStyleValue === 'object') {
			return tokenStyleValue;
		}
		return undefined;
M
Martin Aeschlimann 已提交
237 238
	}

239 240 241 242 243 244 245 246 247
	private getTokenColorIndex(): TokenColorIndex {
		// collect all colors that tokens can have
		if (!this.tokenColorIndex) {
			const index = new TokenColorIndex();
			this.tokenColors.forEach(rule => {
				index.add(rule.settings.foreground);
				index.add(rule.settings.background);
			});

248
			this.semanticTokenRules.forEach(r => index.add(r.style.foreground));
249 250 251 252 253 254
			tokenClassificationRegistry.getTokenStylingDefaultRules().forEach(r => {
				const defaultColor = r.defaults[this.type];
				if (defaultColor && typeof defaultColor === 'object') {
					index.add(defaultColor.foreground);
				}
			});
255
			this.customSemanticTokenRules.forEach(r => index.add(r.style.foreground));
256 257 258 259

			this.tokenColorIndex = index;
		}
		return this.tokenColorIndex;
260 261
	}

262 263 264 265
	public get tokenColorMap(): string[] {
		return this.getTokenColorIndex().asArray();
	}

M
Martin Aeschlimann 已提交
266
	public getTokenStyleMetadata(typeWithLanguage: string, modifiers: string[], defaultLanguage: string, useDefault = true, definitions: TokenStyleDefinitions = {}): ITokenStyle | undefined {
M
Martin Aeschlimann 已提交
267 268
		const { type, language } = parseClassifierString(typeWithLanguage, defaultLanguage);
		let style = this.getTokenStyle(type, modifiers, language, useDefault, definitions);
269 270
		if (!style) {
			return undefined;
271
		}
272 273 274 275 276 277 278

		return {
			foreground: this.getTokenColorIndex().get(style.foreground),
			bold: style.bold,
			underline: style.underline,
			italic: style.italic
		};
279 280
	}

281 282
	public getTokenStylingRuleScope(rule: SemanticTokenRule): 'setting' | 'theme' | undefined {
		if (this.customSemanticTokenRules.indexOf(rule) !== -1) {
283 284
			return 'setting';
		}
285
		if (this.semanticTokenRules.indexOf(rule) !== -1) {
286 287 288 289 290
			return 'theme';
		}
		return undefined;
	}

291 292
	public getDefault(colorId: ColorIdentifier): Color | undefined {
		return colorRegistry.resolveDefaultColor(colorId, this);
M
Martin Aeschlimann 已提交
293 294
	}

295 296

	public resolveScopes(scopes: ProbeScope[], definitions?: TextMateThemingRuleDefinitions): TokenStyle | undefined {
M
Martin Aeschlimann 已提交
297

M
Martin Aeschlimann 已提交
298
		if (!this.themeTokenScopeMatchers) {
M
Martin Aeschlimann 已提交
299
			this.themeTokenScopeMatchers = this.themeTokenColors.map(getScopeMatcher);
M
Martin Aeschlimann 已提交
300
		}
M
Martin Aeschlimann 已提交
301
		if (!this.customTokenScopeMatchers) {
M
Martin Aeschlimann 已提交
302
			this.customTokenScopeMatchers = this.customTokenColors.map(getScopeMatcher);
M
Martin Aeschlimann 已提交
303 304
		}

M
Martin Aeschlimann 已提交
305
		for (let scope of scopes) {
306 307
			let foreground: string | undefined = undefined;
			let fontStyle: string | undefined = undefined;
M
Martin Aeschlimann 已提交
308 309
			let foregroundScore = -1;
			let fontStyleScore = -1;
310 311
			let fontStyleThemingRule: ITextMateThemingRule | undefined = undefined;
			let foregroundThemingRule: ITextMateThemingRule | undefined = undefined;
M
Martin Aeschlimann 已提交
312

313
			function findTokenStyleForScopeInScopes(scopeMatchers: Matcher<ProbeScope>[], themingRules: ITextMateThemingRule[]) {
M
Martin Aeschlimann 已提交
314 315 316
				for (let i = 0; i < scopeMatchers.length; i++) {
					const score = scopeMatchers[i](scope);
					if (score >= 0) {
317 318
						const themingRule = themingRules[i];
						const settings = themingRules[i].settings;
M
Martin Aeschlimann 已提交
319 320
						if (score >= foregroundScore && settings.foreground) {
							foreground = settings.foreground;
321
							foregroundScore = score;
322
							foregroundThemingRule = themingRule;
M
Martin Aeschlimann 已提交
323 324 325
						}
						if (score >= fontStyleScore && types.isString(settings.fontStyle)) {
							fontStyle = settings.fontStyle;
326
							fontStyleScore = score;
327
							fontStyleThemingRule = themingRule;
M
Martin Aeschlimann 已提交
328 329 330 331 332 333
						}
					}
				}
			}
			findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors);
			findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors);
334
			if (foreground !== undefined || fontStyle !== undefined) {
335 336 337 338 339 340
				if (definitions) {
					definitions.foreground = foregroundThemingRule;
					definitions.bold = definitions.italic = definitions.underline = fontStyleThemingRule;
					definitions.scope = scope;
				}

M
Martin Aeschlimann 已提交
341
				return TokenStyle.fromSettings(foreground, fontStyle);
M
Martin Aeschlimann 已提交
342 343 344
			}
		}
		return undefined;
M
Martin Aeschlimann 已提交
345 346
	}

347 348
	public defines(colorId: ColorIdentifier): boolean {
		return this.customColorMap.hasOwnProperty(colorId) || this.colorMap.hasOwnProperty(colorId);
349 350
	}

351 352 353
	public setCustomizations(settings: ThemeConfiguration) {
		this.setCustomColors(settings.colorCustomizations);
		this.setCustomTokenColors(settings.tokenColorCustomizations);
354
		this.setCustomSemanticTokenColors(settings.semanticTokenColorCustomizations, settings.experimentalSemanticTokenColorCustomizations);
355 356
	}

357 358
	public setCustomColors(colors: IColorCustomizations) {
		this.customColorMap = {};
359
		this.overwriteCustomColors(colors);
360 361 362 363

		const themeSpecificColors = colors[`[${this.settingsId}]`] as IColorCustomizations;
		if (types.isObject(themeSpecificColors)) {
			this.overwriteCustomColors(themeSpecificColors);
364
		}
365 366

		this.tokenColorIndex = undefined;
367
		this.textMateThemingRules = undefined;
M
Martin Aeschlimann 已提交
368
		this.customTokenScopeMatchers = undefined;
369 370 371
	}

	private overwriteCustomColors(colors: IColorCustomizations) {
372 373 374
		for (let id in colors) {
			let colorVal = colors[id];
			if (typeof colorVal === 'string') {
375
				this.customColorMap[id] = Color.fromHex(colorVal);
376 377 378 379
			}
		}
	}

380
	public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) {
381
		this.customTokenColors = [];
382
		this.customSemanticHighlightingDeprecated = undefined;
M
Martin Aeschlimann 已提交
383

384 385 386 387 388 389 390
		// first add the non-theme specific settings
		this.addCustomTokenColors(customTokenColors);

		// append theme specific settings. Last rules will win.
		const themeSpecificTokenColors = customTokenColors[`[${this.settingsId}]`] as ITokenColorCustomizations;
		if (types.isObject(themeSpecificTokenColors)) {
			this.addCustomTokenColors(themeSpecificTokenColors);
391
		}
392 393 394

		this.tokenColorIndex = undefined;
		this.textMateThemingRules = undefined;
M
Martin Aeschlimann 已提交
395
		this.customTokenScopeMatchers = undefined;
396 397
	}

398 399 400
	public setCustomSemanticTokenColors(semanticTokenColors: ISemanticTokenColorCustomizations | undefined, experimental?: IExperimentalSemanticTokenColorCustomizations) {
		this.customSemanticTokenRules = [];
		this.customSemanticHighlighting = undefined;
401

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
		if (experimental) { // apply deprecated settings first
			this.readSemanticTokenRules(experimental);
			const themeSpecificColors = experimental[`[${this.settingsId}]`] as IExperimentalSemanticTokenColorCustomizations;
			if (types.isObject(themeSpecificColors)) {
				this.readSemanticTokenRules(themeSpecificColors);
			}
		}
		if (semanticTokenColors) {
			this.customSemanticHighlighting = semanticTokenColors.enabled;
			if (semanticTokenColors.rules) {
				this.readSemanticTokenRules(semanticTokenColors.rules);
			}
			const themeSpecificColors = semanticTokenColors[`[${this.settingsId}]`] as ISemanticTokenColorCustomizations;
			if (types.isObject(themeSpecificColors)) {
				if (themeSpecificColors.enabled !== undefined) {
					this.customSemanticHighlighting = themeSpecificColors.enabled;
				}
				if (themeSpecificColors.rules) {
					this.readSemanticTokenRules(themeSpecificColors.rules);
				}
			}
423
		}
424 425

		this.tokenColorIndex = undefined;
426
		this.textMateThemingRules = undefined;
M
Martin Aeschlimann 已提交
427 428
	}

429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444

	private readSemanticTokenRules(tokenStylingRuleSection: ISemanticTokenRules) {
		for (let key in tokenStylingRuleSection) {
			if (key[0] !== '[') { // still do this test until experimental settings are gone
				try {
					const rule = readSemanticTokenRule(key, tokenStylingRuleSection[key]);
					if (rule) {
						this.customSemanticTokenRules.push(rule);
					}
				} catch (e) {
					// invalid selector, ignore
				}
			}
		}
	}

445
	private addCustomTokenColors(customTokenColors: ITokenColorCustomizations) {
446 447 448
		// Put the general customizations such as comments, strings, etc. first so that
		// they can be overridden by specific customizations like "string.interpolated"
		for (let tokenGroup in tokenGroupToScopesMap) {
449 450
			const group = <keyof typeof tokenGroupToScopesMap>tokenGroup; // TS doesn't type 'tokenGroup' properly
			let value = customTokenColors[group];
451 452
			if (value) {
				let settings = typeof value === 'string' ? { foreground: value } : value;
453
				let scopes = tokenGroupToScopesMap[group];
454
				for (let scope of scopes) {
455
					this.customTokenColors.push({ scope, settings });
456
				}
457
			}
458
		}
459

460 461 462 463 464 465 466 467
		// specific customizations
		if (Array.isArray(customTokenColors.textMateRules)) {
			for (let rule of customTokenColors.textMateRules) {
				if (rule.scope && rule.settings) {
					this.customTokenColors.push(rule);
				}
			}
		}
468
		if (customTokenColors.semanticHighlighting !== undefined) {
469
			this.customSemanticHighlightingDeprecated = customTokenColors.semanticHighlighting;
470
		}
C
Cody Hoover 已提交
471 472
	}

473 474
	public ensureLoaded(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise<void> {
		return !this.isLoaded ? this.load(extensionResourceLoaderService) : Promise.resolve(undefined);
475 476
	}

477 478
	public reload(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise<void> {
		return this.load(extensionResourceLoaderService);
479 480
	}

481
	private load(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise<void> {
482 483
		if (!this.location) {
			return Promise.resolve(undefined);
M
Martin Aeschlimann 已提交
484
		}
485
		this.themeTokenColors = [];
M
Martin Aeschlimann 已提交
486
		this.clearCaches();
487 488 489 490

		const result = {
			colors: {},
			textMateRules: [],
491
			semanticTokenRules: [],
492
			semanticHighlighting: false
493 494
		};
		return _loadColorTheme(extensionResourceLoaderService, this.location, result).then(_ => {
495
			this.isLoaded = true;
496
			this.semanticTokenRules = result.semanticTokenRules;
497 498
			this.colorMap = result.colors;
			this.themeTokenColors = result.textMateRules;
499
			this.themeSemanticHighlighting = result.semanticHighlighting;
500
		});
M
Martin Aeschlimann 已提交
501 502
	}

503 504 505 506 507 508 509
	public clearCaches() {
		this.tokenColorIndex = undefined;
		this.textMateThemingRules = undefined;
		this.themeTokenScopeMatchers = undefined;
		this.customTokenScopeMatchers = undefined;
	}

M
Martin Aeschlimann 已提交
510
	toStorage(storageService: IStorageService) {
511
		let colorMapData: { [key: string]: string } = {};
512
		for (let key in this.colorMap) {
J
Joao Moreno 已提交
513
			colorMapData[key] = Color.Format.CSS.formatHexA(this.colorMap[key], true);
514
		}
515
		// no need to persist custom colors, they will be taken from the settings
M
Martin Aeschlimann 已提交
516
		const value = JSON.stringify({
517 518 519
			id: this.id,
			label: this.label,
			settingsId: this.settingsId,
520
			selector: this.id.split(' ').join('.'), // to not break old clients
521
			themeTokenColors: this.themeTokenColors,
522
			semanticTokenRules: this.semanticTokenRules.map(SemanticTokenRule.toJSONObject),
523 524
			extensionData: ExtensionData.toJSONObject(this.extensionData),
			location: this.location?.toJSON(),
525
			themeSemanticHighlighting: this.themeSemanticHighlighting,
526 527
			colorMap: colorMapData,
			watch: this.watch
528
		});
M
Martin Aeschlimann 已提交
529
		storageService.store(PERSISTED_THEME_STORAGE_KEY, value, StorageScope.GLOBAL);
530 531
	}

532
	hasEqualData(other: ColorThemeData) {
533 534
		return objects.equals(this.colorMap, other.colorMap)
			&& objects.equals(this.themeTokenColors, other.themeTokenColors)
535
			&& arrays.equals(this.semanticTokenRules, other.semanticTokenRules, SemanticTokenRule.equals)
536
			&& this.themeSemanticHighlighting === other.themeSemanticHighlighting;
537
	}
538

539 540 541 542
	get baseTheme(): string {
		return this.id.split(' ')[0];
	}

543
	get type(): ColorScheme {
544
		switch (this.baseTheme) {
545 546 547
			case VS_LIGHT_THEME: return ColorScheme.LIGHT;
			case VS_HC_THEME: return ColorScheme.HIGH_CONTRAST;
			default: return ColorScheme.DARK;
548 549
		}
	}
M
Martin Aeschlimann 已提交
550

551 552
	// constructors

553
	static createUnloadedThemeForThemeType(themeType: ColorScheme, colorMap?: { [id: string]: string }): ColorThemeData {
554
		return ColorThemeData.createUnloadedTheme(getThemeTypeSelector(themeType), colorMap);
555 556
	}

557
	static createUnloadedTheme(id: string, colorMap?: { [id: string]: string }): ColorThemeData {
558
		let themeData = new ColorThemeData(id, '', '__' + id);
559
		themeData.isLoaded = false;
560
		themeData.themeTokenColors = [];
561
		themeData.watch = false;
562 563 564 565 566
		if (colorMap) {
			for (let id in colorMap) {
				themeData.colorMap[id] = Color.fromHex(colorMap[id]);
			}
		}
567 568
		return themeData;
	}
569

570
	static createLoadedEmptyTheme(id: string, settingsId: string): ColorThemeData {
571
		let themeData = new ColorThemeData(id, '', settingsId);
572
		themeData.isLoaded = true;
573
		themeData.themeTokenColors = [];
574
		themeData.watch = false;
575 576 577
		return themeData;
	}

M
Martin Aeschlimann 已提交
578 579 580 581 582
	static fromStorageData(storageService: IStorageService): ColorThemeData | undefined {
		const input = storageService.get(PERSISTED_THEME_STORAGE_KEY, StorageScope.GLOBAL);
		if (!input) {
			return undefined;
		}
583 584
		try {
			let data = JSON.parse(input);
585
			let theme = new ColorThemeData('', '', '');
586 587 588 589 590 591 592 593 594
			for (let key in data) {
				switch (key) {
					case 'colorMap':
						let colorMapData = data[key];
						for (let id in colorMapData) {
							theme.colorMap[id] = Color.fromHex(colorMapData[id]);
						}
						break;
					case 'themeTokenColors':
595
					case 'id': case 'label': case 'settingsId': case 'watch': case 'themeSemanticHighlighting':
M
Matt Bierner 已提交
596
						(theme as any)[key] = data[key];
597
						break;
598
					case 'semanticTokenRules':
599 600 601
						const rulesData = data[key];
						if (Array.isArray(rulesData)) {
							for (let d of rulesData) {
602
								const rule = SemanticTokenRule.fromJSONObject(tokenClassificationRegistry, d);
603
								if (rule) {
604
									theme.semanticTokenRules.push(rule);
605 606 607 608
								}
							}
						}
						break;
609 610 611 612 613 614
					case 'location':
						theme.location = URI.revive(data.location);
						break;
					case 'extensionData':
						theme.extensionData = ExtensionData.fromJSONObject(data.extensionData);
						break;
615
				}
616
			}
617 618 619
			if (!theme.id || !theme.settingsId) {
				return undefined;
			}
620 621
			return theme;
		} catch (e) {
622
			return undefined;
623 624 625
		}
	}

A
Alex Dima 已提交
626
	static fromExtensionTheme(theme: IThemeExtensionPoint, colorThemeLocation: URI, extensionData: ExtensionData): ColorThemeData {
627 628 629 630 631 632
		const baseTheme: string = theme['uiTheme'] || 'vs-dark';
		const themeSelector = toCSSSelector(extensionData.extensionId, theme.path);
		const id = `${baseTheme} ${themeSelector}`;
		const label = theme.label || basename(theme.path);
		const settingsId = theme.id || label;
		const themeData = new ColorThemeData(id, label, settingsId);
633
		themeData.description = theme.description;
634
		themeData.watch = theme._watch === true;
A
Alex Dima 已提交
635
		themeData.location = colorThemeLocation;
636 637 638 639
		themeData.extensionData = extensionData;
		themeData.isLoaded = false;
		return themeData;
	}
640 641
}

642
function toCSSSelector(extensionId: string, path: string) {
643
	if (path.startsWith('./')) {
644 645 646 647 648
		path = path.substr(2);
	}
	let str = `${extensionId}-${path}`;

	//remove all characters that are not allowed in css
649 650 651 652 653 654 655
	str = str.replace(/[^_\-a-zA-Z0-9]/g, '-');
	if (str.charAt(0).match(/[0-9\-]/)) {
		str = '_' + str;
	}
	return str;
}

656
async function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, semanticTokenRules: SemanticTokenRule[], semanticHighlighting: boolean }): Promise<any> {
B
Benjamin Pasero 已提交
657
	if (resources.extname(themeLocation) === '.json') {
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
		const content = await extensionResourceLoaderService.readExtensionResource(themeLocation);
		let errors: Json.ParseError[] = [];
		let contentValue = Json.parse(content, errors);
		if (errors.length > 0) {
			return Promise.reject(new Error(nls.localize('error.cannotparsejson', "Problems parsing JSON theme file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', '))));
		} else if (Json.getNodeType(contentValue) !== 'object') {
			return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for JSON theme file: Object expected.")));
		}
		if (contentValue.include) {
			await _loadColorTheme(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), result);
		}
		if (Array.isArray(contentValue.settings)) {
			convertSettings(contentValue.settings, result);
			return null;
		}
		result.semanticHighlighting = result.semanticHighlighting || contentValue.semanticHighlighting;
		let colors = contentValue.colors;
		if (colors) {
			if (typeof colors !== 'object') {
				return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.colors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'colors' is not of type 'object'.", themeLocation.toString())));
M
Martin Aeschlimann 已提交
678
			}
679 680 681 682 683
			// new JSON color themes format
			for (let colorId in colors) {
				let colorHex = colors[colorId];
				if (typeof colorHex === 'string') { // ignore colors tht are null
					result.colors[colorId] = Color.fromHex(colors[colorId]);
684
				}
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
			}
		}
		let tokenColors = contentValue.tokenColors;
		if (tokenColors) {
			if (Array.isArray(tokenColors)) {
				result.textMateRules.push(...tokenColors);
			} else if (typeof tokenColors === 'string') {
				await _loadSyntaxTokens(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), tokenColors), result);
			} else {
				return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.tokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'tokenColors' should be either an array specifying colors or a path to a TextMate theme file", themeLocation.toString())));
			}
		}
		let semanticTokenColors = contentValue.semanticTokenColors;
		if (semanticTokenColors && typeof semanticTokenColors === 'object') {
			for (let key in semanticTokenColors) {
				try {
701
					const rule = readSemanticTokenRule(key, semanticTokenColors[key]);
702
					if (rule) {
703
						result.semanticTokenRules.push(rule);
704
					}
705 706
				} catch (e) {
					return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.semanticTokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'semanticTokenColors' conatains a invalid selector", themeLocation.toString())));
707
				}
708 709
			}
		}
710
	} else {
711
		return _loadSyntaxTokens(extensionResourceLoaderService, themeLocation, result);
712 713 714
	}
}

715
function _loadSyntaxTokens(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap }): Promise<any> {
716
	return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => {
717
		try {
718
			let contentValue = parsePList(content);
719
			let settings: ITextMateThemingRule[] = contentValue.settings;
720 721
			if (!Array.isArray(settings)) {
				return Promise.reject(new Error(nls.localize('error.plist.invalidformat', "Problem parsing tmTheme file: {0}. 'settings' is not array.")));
M
Martin Aeschlimann 已提交
722
			}
723
			convertSettings(settings, result);
724 725 726 727
			return Promise.resolve(null);
		} catch (e) {
			return Promise.reject(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message)));
		}
728
	}, error => {
M
Martin Aeschlimann 已提交
729
		return Promise.reject(new Error(nls.localize('error.cannotload', "Problems loading tmTheme file {0}: {1}", themeLocation.toString(), error.message)));
M
Martin Aeschlimann 已提交
730 731
	});
}
732

733
let defaultThemeColors: { [baseTheme: string]: ITextMateThemingRule[] } = {
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
	'light': [
		{ scope: 'token.info-token', settings: { foreground: '#316bcd' } },
		{ scope: 'token.warn-token', settings: { foreground: '#cd9731' } },
		{ scope: 'token.error-token', settings: { foreground: '#cd3131' } },
		{ scope: 'token.debug-token', settings: { foreground: '#800080' } }
	],
	'dark': [
		{ scope: 'token.info-token', settings: { foreground: '#6796e6' } },
		{ scope: 'token.warn-token', settings: { foreground: '#cd9731' } },
		{ scope: 'token.error-token', settings: { foreground: '#f44747' } },
		{ scope: 'token.debug-token', settings: { foreground: '#b267e6' } }
	],
	'hc': [
		{ scope: 'token.info-token', settings: { foreground: '#6796e6' } },
		{ scope: 'token.warn-token', settings: { foreground: '#008000' } },
		{ scope: 'token.error-token', settings: { foreground: '#FF0000' } },
		{ scope: 'token.debug-token', settings: { foreground: '#b267e6' } }
	],
M
Martin Aeschlimann 已提交
752 753
};

M
Martin Aeschlimann 已提交
754
const noMatch = (_scope: ProbeScope) => -1;
M
Martin Aeschlimann 已提交
755

M
Martin Aeschlimann 已提交
756 757 758
function nameMatcher(identifers: string[], scope: ProbeScope): number {
	function findInIdents(s: string, lastIndent: number): number {
		for (let i = lastIndent - 1; i >= 0; i--) {
759
			if (scopesAreMatching(s, identifers[i])) {
M
Martin Aeschlimann 已提交
760 761 762 763 764
				return i;
			}
		}
		return -1;
	}
M
more  
Martin Aeschlimann 已提交
765
	if (scope.length < identifers.length) {
M
Martin Aeschlimann 已提交
766 767 768 769 770
		return -1;
	}
	let lastScopeIndex = scope.length - 1;
	let lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], identifers.length);
	if (lastIdentifierIndex >= 0) {
771
		const score = (lastIdentifierIndex + 1) * 0x10000 + identifers[lastIdentifierIndex].length;
M
Martin Aeschlimann 已提交
772 773 774 775
		while (lastScopeIndex >= 0) {
			lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], lastIdentifierIndex);
			if (lastIdentifierIndex === -1) {
				return -1;
M
more  
Martin Aeschlimann 已提交
776 777
			}
		}
M
Martin Aeschlimann 已提交
778 779 780
		return score;
	}
	return -1;
M
Martin Aeschlimann 已提交
781 782
}

M
Martin Aeschlimann 已提交
783

M
Martin Aeschlimann 已提交
784 785 786 787 788 789 790 791 792 793 794
function scopesAreMatching(thisScopeName: string, scopeName: string): boolean {
	if (!thisScopeName) {
		return false;
	}
	if (thisScopeName === scopeName) {
		return true;
	}
	const len = scopeName.length;
	return thisScopeName.length > len && thisScopeName.substr(0, len) === scopeName && thisScopeName[len] === '.';
}

795
function getScopeMatcher(rule: ITextMateThemingRule): Matcher<ProbeScope> {
M
more  
Martin Aeschlimann 已提交
796 797
	const ruleScope = rule.scope;
	if (!ruleScope || !rule.settings) {
M
Martin Aeschlimann 已提交
798 799
		return noMatch;
	}
M
more  
Martin Aeschlimann 已提交
800
	const matchers: MatcherWithPriority<ProbeScope>[] = [];
M
Martin Aeschlimann 已提交
801 802 803 804 805 806
	if (Array.isArray(ruleScope)) {
		for (let rs of ruleScope) {
			createMatchers(rs, nameMatcher, matchers);
		}
	} else {
		createMatchers(ruleScope, nameMatcher, matchers);
M
Martin Aeschlimann 已提交
807
	}
M
Martin Aeschlimann 已提交
808

809 810 811
	if (matchers.length === 0) {
		return noMatch;
	}
M
Martin Aeschlimann 已提交
812
	return (scope: ProbeScope) => {
813 814 815
		let max = matchers[0].matcher(scope);
		for (let i = 1; i < matchers.length; i++) {
			max = Math.max(max, matchers[i].matcher(scope));
M
Martin Aeschlimann 已提交
816 817 818
		}
		return max;
	};
M
Martin Aeschlimann 已提交
819 820
}

821
function readSemanticTokenRule(selectorString: string, settings: ISemanticTokenColorizationSetting | string | boolean | undefined): SemanticTokenRule | undefined {
822 823 824 825
	const selector = tokenClassificationRegistry.parseTokenSelector(selectorString);
	let style: TokenStyle | undefined;
	if (typeof settings === 'string') {
		style = TokenStyle.fromSettings(settings, undefined);
826 827
	} else if (isSemanticTokenColorizationSetting(settings)) {
		style = TokenStyle.fromSettings(settings.foreground, settings.fontStyle, settings.bold, settings.underline, settings.italic);
828 829 830 831 832 833
	}
	if (style) {
		return { selector, style };
	}
	return undefined;
}
834

835 836 837
function isSemanticTokenColorizationSetting(style: any): style is ISemanticTokenColorizationSetting {
	return style && (types.isString(style.foreground) || types.isString(style.fontStyle) || types.isBoolean(style.italic)
		|| types.isBoolean(style.underline) || types.isBoolean(style.bold));
838
}
839 840 841 842 843 844 845 846 847 848 849 850 851 852


class TokenColorIndex {

	private _lastColorId: number;
	private _id2color: string[];
	private _color2id: { [color: string]: number; };

	constructor() {
		this._lastColorId = 0;
		this._id2color = [];
		this._color2id = Object.create(null);
	}

853 854 855
	public add(color: string | Color | undefined): number {
		color = normalizeColor(color);
		if (color === undefined) {
856 857 858 859 860 861 862 863 864 865 866 867 868 869
			return 0;
		}

		let value = this._color2id[color];
		if (value) {
			return value;
		}
		value = ++this._lastColorId;
		this._color2id[color] = value;
		this._id2color[value] = color;
		return value;
	}

	public get(color: string | Color | undefined): number {
870
		color = normalizeColor(color);
871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887
		if (color === undefined) {
			return 0;
		}
		let value = this._color2id[color];
		if (value) {
			return value;
		}
		console.log(`Color ${color} not in index.`);
		return 0;
	}

	public asArray(): string[] {
		return this._id2color.slice(0);
	}

}

888 889 890 891
function normalizeColor(color: string | Color | undefined | null): string | undefined {
	if (!color) {
		return undefined;
	}
892 893 894
	if (typeof color !== 'string') {
		color = Color.Format.CSS.formatHexA(color, true);
	}
895 896
	const len = color.length;
	if (color.charCodeAt(0) !== CharCode.Hash || (len !== 4 && len !== 5 && len !== 7 && len !== 9)) {
897 898 899
		return undefined;
	}
	let result = [CharCode.Hash];
900

901 902 903 904 905 906
	for (let i = 1; i < len; i++) {
		const upper = hexUpper(color.charCodeAt(i));
		if (!upper) {
			return undefined;
		}
		result.push(upper);
907
		if (len === 4 || len === 5) {
908 909 910 911 912 913 914
			result.push(upper);
		}
	}

	if (result.length === 9 && result[7] === CharCode.F && result[8] === CharCode.F) {
		result.length = 7;
	}
915
	return String.fromCharCode(...result);
916 917 918 919 920 921 922 923 924
}

function hexUpper(charCode: CharCode): number {
	if (charCode >= CharCode.Digit0 && charCode <= CharCode.Digit9 || charCode >= CharCode.A && charCode <= CharCode.F) {
		return charCode;
	} else if (charCode >= CharCode.a && charCode <= CharCode.f) {
		return charCode - CharCode.a + CharCode.A;
	}
	return 0;
925
}