workbenchThemeService.ts 35.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';

J
Johannes Rieken 已提交
7
import { TPromise, Promise } from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
8
import nls = require('vs/nls');
9
import * as Paths from 'path';
10
import Json = require('vs/base/common/json');
M
Martin Aeschlimann 已提交
11
import * as types from 'vs/base/common/types';
12
import * as objects from 'vs/base/common/objects';
J
Johannes Rieken 已提交
13 14
import { IThemeExtensionPoint } from 'vs/platform/theme/common/themeExtensionPoint';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
15
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry';
16
import { IWorkbenchThemeService, IColorTheme, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_COLOR_THEME_SETTING, ITokenColorizationRule, CUSTOM_THEME_ID } from 'vs/workbench/services/themes/common/workbenchThemeService';
17
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
J
Johannes Rieken 已提交
18 19 20 21
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Registry } from 'vs/platform/platform';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
B
Benjamin Pasero 已提交
22
import errors = require('vs/base/common/errors');
M
Martin Aeschlimann 已提交
23 24 25
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
26 27
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IMessageService } from 'vs/platform/message/common/message';
28
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
29
import Severity from 'vs/base/common/severity';
30
import { ColorThemeData, fromStorageData, fromUserTheme, fromExtensionTheme } from './colorThemeData';
31
import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService';
32
import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
J
Johannes Rieken 已提交
33 34 35

import { $ } from 'vs/base/browser/builder';
import Event, { Emitter } from 'vs/base/common/event';
E
Erich Gamma 已提交
36 37 38

import pfs = require('vs/base/node/pfs');

39 40
import colorThemeSchema = require('vs/workbench/services/themes/common/colorThemeSchema');
import fileIconThemeSchema = require('vs/workbench/services/themes/common/fileIconThemeSchema');
41
import { IDisposable } from 'vs/base/common/lifecycle';
42

43

E
Erich Gamma 已提交
44 45
// implementation

M
Martin Aeschlimann 已提交
46
const DEFAULT_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json';
47
const DEFAULT_THEME_SETTING_VALUE = 'Default Dark+';
M
Martin Aeschlimann 已提交
48

49 50
const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData';

51 52 53
const defaultThemeExtensionId = 'vscode-theme-defaults';
const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults';

54 55
const fileIconsEnabledClass = 'file-icons-enabled';

56 57
const themingRegistry = Registry.as<IThemingRegistry>(ThemingExtensions.ThemingContribution);

J
Johannes Rieken 已提交
58
function validateThemeId(theme: string): string {
59 60
	// migrations
	switch (theme) {
61 62 63
		case VS_LIGHT_THEME: return `vs ${defaultThemeExtensionId}-themes-light_vs-json`;
		case VS_DARK_THEME: return `vs-dark ${defaultThemeExtensionId}-themes-dark_vs-json`;
		case VS_HC_THEME: return `hc-black ${defaultThemeExtensionId}-themes-hc_black-json`;
64 65 66 67 68 69
		case `vs ${oldDefaultThemeExtensionId}-themes-light_plus-tmTheme`: return `vs ${defaultThemeExtensionId}-themes-light_plus-json`;
		case `vs-dark ${oldDefaultThemeExtensionId}-themes-dark_plus-tmTheme`: return `vs-dark ${defaultThemeExtensionId}-themes-dark_plus-json`;
	}
	return theme;
}

A
Alex Dima 已提交
70
let themesExtPoint = ExtensionsRegistry.registerExtensionPoint<IThemeExtensionPoint[]>('themes', [], {
E
Erich Gamma 已提交
71 72 73 74
	description: nls.localize('vscode.extension.contributes.themes', 'Contributes textmate color themes.'),
	type: 'array',
	items: {
		type: 'object',
M
Martin Aeschlimann 已提交
75
		defaultSnippets: [{ body: { label: '${1:label}', id: '${2:id}', uiTheme: VS_DARK_THEME, path: './themes/${3:id}.tmTheme.' } }],
E
Erich Gamma 已提交
76
		properties: {
M
Martin Aeschlimann 已提交
77 78 79 80
			id: {
				description: nls.localize('vscode.extension.contributes.themes.id', 'Id of the icon theme as used in the user settings.'),
				type: 'string'
			},
E
Erich Gamma 已提交
81 82 83 84 85
			label: {
				description: nls.localize('vscode.extension.contributes.themes.label', 'Label of the color theme as shown in the UI.'),
				type: 'string'
			},
			uiTheme: {
M
Martin Aeschlimann 已提交
86
				description: nls.localize('vscode.extension.contributes.themes.uiTheme', 'Base theme defining the colors around the editor: \'vs\' is the light color theme, \'vs-dark\' is the dark color theme. \'hc-black\' is the dark high contrast theme.'),
87
				enum: [VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME]
E
Erich Gamma 已提交
88 89 90 91 92
			},
			path: {
				description: nls.localize('vscode.extension.contributes.themes.path', 'Path of the tmTheme file. The path is relative to the extension folder and is typically \'./themes/themeFile.tmTheme\'.'),
				type: 'string'
			}
M
Martin Aeschlimann 已提交
93 94 95 96 97
		},
		required: ['path', 'uiTheme']
	}
});

A
Alex Dima 已提交
98
let iconThemeExtPoint = ExtensionsRegistry.registerExtensionPoint<IThemeExtensionPoint[]>('iconThemes', [], {
99
	description: nls.localize('vscode.extension.contributes.iconThemes', 'Contributes file icon themes.'),
M
Martin Aeschlimann 已提交
100 101 102
	type: 'array',
	items: {
		type: 'object',
103
		defaultSnippets: [{ body: { id: '${1:id}', label: '${2:label}', path: './fileicons/${3:id}-icon-theme.json' } }],
M
Martin Aeschlimann 已提交
104 105
		properties: {
			id: {
106
				description: nls.localize('vscode.extension.contributes.iconThemes.id', 'Id of the icon theme as used in the user settings.'),
M
Martin Aeschlimann 已提交
107 108 109
				type: 'string'
			},
			label: {
110
				description: nls.localize('vscode.extension.contributes.iconThemes.label', 'Label of the icon theme as shown in the UI.'),
M
Martin Aeschlimann 已提交
111 112 113
				type: 'string'
			},
			path: {
114
				description: nls.localize('vscode.extension.contributes.iconThemes.path', 'Path of the icon theme definition file. The path is relative to the extension folder and is typically \'./icons/awesome-icon-theme.json\'.'),
M
Martin Aeschlimann 已提交
115 116 117
				type: 'string'
			}
		},
118
		required: ['path', 'id']
E
Erich Gamma 已提交
119 120 121
	}
});

M
Martin Aeschlimann 已提交
122
interface IInternalIconThemeData extends IFileIconTheme {
123 124
	id: string;
	label: string;
M
Martin Aeschlimann 已提交
125
	settingsId: string;
126 127 128 129
	description?: string;
	hasFileIcons?: boolean;
	hasFolderIcons?: boolean;
	isLoaded: boolean;
M
Martin Aeschlimann 已提交
130 131 132
	path?: string;
	styleSheetContent?: string;
	extensionData: ExtensionData;
133 134
}

135
interface IconDefinition {
M
Martin Aeschlimann 已提交
136
	iconPath: string;
137 138
	fontColor: string;
	fontCharacter: string;
139
	fontSize: string;
140 141 142 143 144 145 146 147
	fontId: string;
}

interface FontDefinition {
	id: string;
	weight: string;
	style: string;
	size: string;
J
Johannes Rieken 已提交
148
	src: { path: string; format: string; }[];
M
Martin Aeschlimann 已提交
149 150
}

151
interface IconsAssociation {
152 153 154
	folder?: string;
	file?: string;
	folderExpanded?: string;
J
Johannes Rieken 已提交
155 156 157 158 159
	folderNames?: { [folderName: string]: string; };
	folderNamesExpanded?: { [folderName: string]: string; };
	fileExtensions?: { [extension: string]: string; };
	fileNames?: { [fileName: string]: string; };
	languageIds?: { [languageId: string]: string; };
M
Martin Aeschlimann 已提交
160 161
}

162
interface IconThemeDocument extends IconsAssociation {
J
Johannes Rieken 已提交
163
	iconDefinitions: { [key: string]: IconDefinition };
164
	fonts: FontDefinition[];
165 166
	light?: IconsAssociation;
	highContrast?: IconsAssociation;
167
}
M
Martin Aeschlimann 已提交
168

169 170 171 172 173 174 175
export interface IUserTheme {
	type: string;
	extends?: string;
	colors?: { [colorId: string]: string; };
	tokenColors?: ITokenColorizationRule[];
}

M
Martin Aeschlimann 已提交
176 177 178
const noFileIconTheme: IFileIconTheme = {
	id: '',
	label: '',
M
Martin Aeschlimann 已提交
179
	settingsId: null,
M
Martin Aeschlimann 已提交
180 181
	hasFileIcons: false,
	hasFolderIcons: false,
M
Martin Aeschlimann 已提交
182 183
	isLoaded: true,
	extensionData: null
M
Martin Aeschlimann 已提交
184 185
};

186 187 188 189
const defaultUserTheme: IUserTheme = {
	type: 'dark'
};

190
export class WorkbenchThemeService implements IWorkbenchThemeService {
191
	_serviceBrand: any;
E
Erich Gamma 已提交
192

193 194
	private extensionsColorThemes: ColorThemeData[];
	private userColorTheme: ColorThemeData;
195
	private currentColorTheme: ColorThemeData;
M
Martin Aeschlimann 已提交
196
	private container: HTMLElement;
M
Martin Aeschlimann 已提交
197
	private onColorThemeChange: Emitter<IColorTheme>;
M
Martin Aeschlimann 已提交
198

M
Martin Aeschlimann 已提交
199 200 201
	private knownIconThemes: IInternalIconThemeData[];
	private currentIconTheme: IFileIconTheme;
	private onFileIconThemeChange: Emitter<IFileIconTheme>;
E
Erich Gamma 已提交
202

203
	private themingParticipantChangeListener: IDisposable;
204
	private _configurationWriter: ConfigurationWriter;
205

M
Martin Aeschlimann 已提交
206
	constructor(
B
Benjamin Pasero 已提交
207
		container: HTMLElement,
J
Johannes Rieken 已提交
208 209
		@IExtensionService private extensionService: IExtensionService,
		@IStorageService private storageService: IStorageService,
210
		@IWindowIPCService private windowService: IWindowIPCService,
M
Martin Aeschlimann 已提交
211
		@IConfigurationService private configurationService: IConfigurationService,
212 213
		@IEnvironmentService private environmentService: IEnvironmentService,
		@IMessageService private messageService: IMessageService,
214 215
		@ITelemetryService private telemetryService: ITelemetryService,
		@IInstantiationService private instantiationService: IInstantiationService) {
216

B
Benjamin Pasero 已提交
217
		this.container = container;
218 219
		this.extensionsColorThemes = [];
		this.userColorTheme = fromUserTheme(defaultUserTheme);
220
		this.onFileIconThemeChange = new Emitter<IFileIconTheme>();
221
		this.knownIconThemes = [];
222 223
		this.onColorThemeChange = new Emitter<IColorTheme>();

M
Martin Aeschlimann 已提交
224 225 226
		this.currentIconTheme = {
			id: '',
			label: '',
M
Martin Aeschlimann 已提交
227
			settingsId: null,
M
Martin Aeschlimann 已提交
228 229
			isLoaded: false,
			hasFileIcons: false,
M
Martin Aeschlimann 已提交
230 231
			hasFolderIcons: false,
			extensionData: null
M
Martin Aeschlimann 已提交
232
		};
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258

		let persistedThemeData = this.storageService.get(PERSISTED_THEME_STORAGE_KEY);
		if (persistedThemeData) {
			let themeData = fromStorageData(persistedThemeData);
			this.updateDynamicCSSRules(themeData);
			this.applyTheme(themeData, null, true);
		} else {
			// In order to avoid paint flashing for tokens, because
			// themes are loaded asynchronously, we need to initialize
			// a color theme document with good defaults until the theme is loaded
			let isLightTheme = (Array.prototype.indexOf.call(document.body.classList, 'vs') >= 0);

			let initialTheme = new ColorThemeData();
			initialTheme.id = isLightTheme ? VS_LIGHT_THEME : VS_DARK_THEME;
			initialTheme.label = '';
			initialTheme.selector = isLightTheme ? VS_LIGHT_THEME : VS_DARK_THEME;
			initialTheme.settingsId = null;
			initialTheme.isLoaded = false;
			initialTheme.tokenColors = [{
				settings: {
					foreground: initialTheme.getColor(editorForeground).toRGBAHex(),
					background: initialTheme.getColor(editorBackground).toRGBAHex()
				}
			}];
			this.currentColorTheme = initialTheme;
		}
E
Erich Gamma 已提交
259 260 261

		themesExtPoint.setHandler((extensions) => {
			for (let ext of extensions) {
262 263 264 265 266 267 268
				let extensionData = {
					extensionId: ext.description.id,
					extensionPublisher: ext.description.publisher,
					extensionName: ext.description.name,
					extensionIsBuiltin: ext.description.isBuiltin
				};
				this.onThemes(ext.description.extensionFolderPath, extensionData, ext.value, ext.collector);
E
Erich Gamma 已提交
269 270
			}
		});
M
Martin Aeschlimann 已提交
271

272
		iconThemeExtPoint.setHandler((extensions) => {
M
Martin Aeschlimann 已提交
273
			for (let ext of extensions) {
274 275 276 277 278 279 280
				let extensionData = {
					extensionId: ext.description.id,
					extensionPublisher: ext.description.publisher,
					extensionName: ext.description.name,
					extensionIsBuiltin: ext.description.isBuiltin
				};
				this.onIconThemes(ext.description.extensionFolderPath, extensionData, ext.value, ext.collector);
M
Martin Aeschlimann 已提交
281 282 283
			}
		});

284 285 286
		this.migrate().then(_ => {
			this.initialize().then(null, errors.onUnexpectedError).then(_ => {
				this.installConfigurationListener();
287
			});
M
Martin Aeschlimann 已提交
288
		});
M
Martin Aeschlimann 已提交
289 290
	}

M
Martin Aeschlimann 已提交
291
	public get onDidColorThemeChange(): Event<IColorTheme> {
M
Martin Aeschlimann 已提交
292
		return this.onColorThemeChange.event;
E
Erich Gamma 已提交
293 294
	}

M
Martin Aeschlimann 已提交
295 296 297 298
	public get onDidFileIconThemeChange(): Event<IFileIconTheme> {
		return this.onFileIconThemeChange.event;
	}

299 300
	public get onThemeChange(): Event<ITheme> {
		return this.onColorThemeChange.event;
301 302
	}

303
	private backupSettings(): TPromise<string> {
304 305 306 307 308
		let resource = this.environmentService.appSettingsPath;
		let backupFileLocation = resource + '-' + new Date().getTime() + '.backup';
		return pfs.readFile(resource).then(content => {
			return pfs.writeFile(backupFileLocation, content).then(_ => backupFileLocation);
		}, err => {
309 310
			if (err && err.code === 'ENOENT') {
				return TPromise.as(null); // ignore, user config file doesn't exist yet
311
			};
312 313 314 315
			return TPromise.wrapError(err);
		});
	}

316 317 318
	private migrate(): TPromise<any> {
		let legacyColorThemeId = this.storageService.get('workbench.theme', StorageScope.GLOBAL, void 0);
		let legacyIconThemeId = this.storageService.get('workbench.iconTheme', StorageScope.GLOBAL, void 0);
319 320
		if (types.isUndefined(legacyColorThemeId) && types.isUndefined(legacyIconThemeId)) {
			return TPromise.as(null);
M
Martin Aeschlimann 已提交
321
		}
322 323 324 325 326 327
		return this.backupSettings().then(backupLocation => {
			let promise = TPromise.as(null);
			if (!types.isUndefined(legacyColorThemeId)) {
				this.storageService.remove('workbench.theme', StorageScope.GLOBAL);
				promise = this.findThemeData(legacyColorThemeId, DEFAULT_THEME_ID).then(theme => {
					let value = theme ? theme.settingsId : DEFAULT_THEME_SETTING_VALUE;
328
					return this.configurationWriter.writeConfiguration(COLOR_THEME_SETTING, value, ConfigurationTarget.USER).then(null, error => null);
329 330 331 332 333 334 335
				});
			}
			if (!types.isUndefined(legacyIconThemeId)) {
				this.storageService.remove('workbench.iconTheme', StorageScope.GLOBAL);
				promise = promise.then(_ => {
					return this._findIconThemeData(legacyIconThemeId).then(theme => {
						let value = theme ? theme.settingsId : null;
336
						return this.configurationWriter.writeConfiguration(ICON_THEME_SETTING, value, ConfigurationTarget.USER).then(null, error => null);
337 338 339 340 341 342 343 344 345 346 347
					});
				});
			}
			return promise.then(_ => {
				if (backupLocation) {
					let message = nls.localize('migration.completed', 'New theme settings have been added to the user settings. Backup available at {0}.', backupLocation);
					this.messageService.show(Severity.Info, message);
					console.log(message);
				}
			});
		});
348
	}
M
Martin Aeschlimann 已提交
349

350
	private initialize(): TPromise<any> {
351

352 353 354 355 356
		let userTheme = this.configurationService.lookup<IUserTheme>(CUSTOM_COLOR_THEME_SETTING).value;
		if (userTheme) {
			this.updateUserTheme(userTheme);
		}

M
Martin Aeschlimann 已提交
357 358 359
		let colorThemeSetting = this.configurationService.lookup<string>(COLOR_THEME_SETTING).value;
		let iconThemeSetting = this.configurationService.lookup<string>(ICON_THEME_SETTING).value || '';

360
		return Promise.join([
M
Martin Aeschlimann 已提交
361 362 363 364 365 366
			this.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => {
				return this.setColorTheme(theme && theme.id, null);
			}),
			this.findIconThemeBySettingsId(iconThemeSetting).then(theme => {
				return this.setFileIconTheme(theme && theme.id, null);
			}),
367
		]);
M
Martin Aeschlimann 已提交
368 369
	}

370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
	private installConfigurationListener() {
		this.configurationService.onDidUpdateConfiguration(e => {
			let colorThemeSetting = this.configurationService.lookup<string>(COLOR_THEME_SETTING).value;
			if (colorThemeSetting !== this.currentColorTheme.settingsId) {
				this.findThemeDataBySettingsId(colorThemeSetting, null).then(theme => {
					if (theme) {
						this.setColorTheme(theme.id, null);
					}
				});
			}

			let iconThemeSetting = this.configurationService.lookup<string>(ICON_THEME_SETTING).value || '';
			if (iconThemeSetting !== this.currentIconTheme.settingsId) {
				this.findIconThemeBySettingsId(iconThemeSetting).then(theme => {
					this.setFileIconTheme(theme && theme.id, null);
				});
			}
387 388 389

			let userTheme = this.configurationService.lookup<IUserTheme>(CUSTOM_COLOR_THEME_SETTING).value || defaultUserTheme;
			this.updateUserTheme(userTheme);
390 391 392
		});
	}

393 394 395 396
	public getTheme(): ITheme {
		return this.getColorTheme();
	}

M
Martin Aeschlimann 已提交
397
	public setColorTheme(themeId: string, settingsTarget: ConfigurationTarget): TPromise<IColorTheme> {
M
Martin Aeschlimann 已提交
398
		if (!themeId) {
M
Martin Aeschlimann 已提交
399
			return TPromise.as(null);
M
Martin Aeschlimann 已提交
400
		}
M
Martin Aeschlimann 已提交
401
		if (themeId === this.currentColorTheme.id && this.currentColorTheme.isLoaded) {
402
			return this.writeColorThemeConfiguration(settingsTarget);
M
Martin Aeschlimann 已提交
403 404 405 406
		}

		themeId = validateThemeId(themeId); // migrate theme ids

407 408 409 410 411
		if (this.themingParticipantChangeListener) {
			this.themingParticipantChangeListener.dispose();
			this.themingParticipantChangeListener = null;
		}

M
Martin Aeschlimann 已提交
412 413
		return this.findThemeData(themeId, DEFAULT_THEME_ID).then(themeData => {
			if (themeData) {
414
				return themeData.ensureLoaded(this).then(_ => {
415 416 417 418 419
					if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) {
						// the loaded theme is identical to the perisisted theme. Don't need to send an event.
						this.currentColorTheme = themeData;
						return TPromise.as(themeData);
					}
420
					this.updateDynamicCSSRules(themeData);
421
					return this.applyTheme(themeData, settingsTarget);
422 423 424
				}, error => {
					return TPromise.wrapError(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.path, error.message));
				});
M
Martin Aeschlimann 已提交
425 426 427 428 429
			}
			return null;
		});
	}

430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
	private updateDynamicCSSRules(themeData: ITheme) {
		let cssRules = [];
		let hasRule = {};
		let ruleCollector = {
			addRule: (rule: string) => {
				if (!hasRule[rule]) {
					cssRules.push(rule);
					hasRule[rule] = true;
				}
			}
		};
		themingRegistry.getThemingParticipants().forEach(p => p(themeData, ruleCollector));
		_applyRules(cssRules.join('\n'), colorThemeRulesClassName);
	}

445 446 447 448 449 450 451 452 453 454
	private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget, silent = false): TPromise<IFileIconTheme> {
		if (this.container) {
			if (this.currentColorTheme) {
				$(this.container).removeClass(this.currentColorTheme.id);
			}
			$(this.container).addClass(newTheme.id);
		}
		this.currentColorTheme = newTheme;
		this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(p => this.updateDynamicCSSRules(this.currentColorTheme));

455 456
		this.sendTelemetry(newTheme.id, newTheme.extensionData, 'color');

457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
		if (silent) {
			return TPromise.as(null);
		}

		this.onColorThemeChange.fire(this.currentColorTheme);

		if (settingsTarget !== ConfigurationTarget.WORKSPACE) {
			this.windowService.broadcast({ channel: 'vscode:changeColorTheme', payload: newTheme.id });
		}
		// remember theme data for a quick restore
		this.storageService.store(PERSISTED_THEME_STORAGE_KEY, newTheme.toStorageData());

		return this.writeColorThemeConfiguration(settingsTarget);
	};

472 473
	private writeColorThemeConfiguration(settingsTarget: ConfigurationTarget): TPromise<IFileIconTheme> {
		if (!types.isUndefinedOrNull(settingsTarget)) {
474
			return this.configurationWriter.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then(_ => this.currentColorTheme);
M
Martin Aeschlimann 已提交
475 476
		}
		return TPromise.as(this.currentColorTheme);
M
Martin Aeschlimann 已提交
477 478
	}

M
Martin Aeschlimann 已提交
479
	public getColorTheme() {
M
Martin Aeschlimann 已提交
480
		return this.currentColorTheme;
A
Alex Dima 已提交
481 482
	}

M
Martin Aeschlimann 已提交
483
	private findThemeData(themeId: string, defaultId?: string): TPromise<ColorThemeData> {
M
Martin Aeschlimann 已提交
484
		return this.getColorThemes().then(allThemes => {
K
katainaka0503 已提交
485
			let defaultTheme: IColorTheme = void 0;
M
Martin Aeschlimann 已提交
486 487 488 489 490 491
			for (let t of allThemes) {
				if (t.id === themeId) {
					return t;
				}
				if (t.id === defaultId) {
					defaultTheme = t;
M
Martin Aeschlimann 已提交
492 493
				}
			}
M
Martin Aeschlimann 已提交
494
			return defaultTheme;
E
Erich Gamma 已提交
495 496 497
		});
	}

498
	public findThemeDataBySettingsId(settingsId: string, defaultId: string): TPromise<ColorThemeData> {
M
Martin Aeschlimann 已提交
499
		return this.getColorThemes().then(allThemes => {
K
katainaka0503 已提交
500
			let defaultTheme: IColorTheme = void 0;
M
Martin Aeschlimann 已提交
501 502 503 504 505 506 507
			for (let t of allThemes) {
				if (t.settingsId === settingsId) {
					return t;
				}
				if (t.id === defaultId) {
					defaultTheme = t;
				}
E
Erich Gamma 已提交
508
			}
M
Martin Aeschlimann 已提交
509
			return defaultTheme;
B
Benjamin Pasero 已提交
510
		});
E
Erich Gamma 已提交
511 512
	}

M
Martin Aeschlimann 已提交
513
	public getColorThemes(): TPromise<IColorTheme[]> {
A
Alex Dima 已提交
514
		return this.extensionService.onReady().then(isReady => {
515
			return this.extensionsColorThemes.concat([this.userColorTheme]);
E
Erich Gamma 已提交
516 517 518
		});
	}

519 520 521 522 523 524 525 526 527
	private updateUserTheme(userTheme: IUserTheme): void {
		if (!objects.equals(this.userColorTheme.userThemeData, userTheme)) {
			this.userColorTheme = fromUserTheme(userTheme);
			if (!!this.currentColorTheme.userThemeData) {
				this.setColorTheme(this.userColorTheme.id, null);
			}
		}
	}

528
	private onThemes(extensionFolderPath: string, extensionData: ExtensionData, themes: IThemeExtensionPoint[], collector: ExtensionMessageCollector): void {
E
Erich Gamma 已提交
529 530 531 532 533 534 535 536 537
		if (!Array.isArray(themes)) {
			collector.error(nls.localize(
				'reqarray',
				"Extension point `{0}` must be an array.",
				themesExtPoint.name
			));
			return;
		}
		themes.forEach(theme => {
M
Martin Aeschlimann 已提交
538
			if (!theme.path || !types.isString(theme.path)) {
E
Erich Gamma 已提交
539 540 541 542 543 544 545 546 547 548
				collector.error(nls.localize(
					'reqpath',
					"Expected string in `contributes.{0}.path`. Provided value: {1}",
					themesExtPoint.name,
					String(theme.path)
				));
				return;
			}
			let normalizedAbsolutePath = Paths.normalize(Paths.join(extensionFolderPath, theme.path));

549
			if (normalizedAbsolutePath.indexOf(Paths.normalize(extensionFolderPath)) !== 0) {
E
Erich Gamma 已提交
550 551
				collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", themesExtPoint.name, normalizedAbsolutePath, extensionFolderPath));
			}
552 553
			let themeData = fromExtensionTheme(theme, normalizedAbsolutePath, extensionData);
			this.extensionsColorThemes.push(themeData);
E
Erich Gamma 已提交
554

555 556 557 558
			colorThemeSettingSchema.enum.push(themeData.settingsId);
			colorThemeSettingSchema.enumDescriptions.push(themeData.description || '');
			extendsSchema.enum.push(themeData.settingsId);
			extendsSchema.enumDescriptions.push(themeData.description || '');
B
Benjamin Pasero 已提交
559
		});
E
Erich Gamma 已提交
560
	}
561

562
	private onIconThemes(extensionFolderPath: string, extensionData: ExtensionData, iconThemes: IThemeExtensionPoint[], collector: ExtensionMessageCollector): void {
563
		if (!Array.isArray(iconThemes)) {
M
Martin Aeschlimann 已提交
564 565 566 567 568 569 570
			collector.error(nls.localize(
				'reqarray',
				"Extension point `{0}` must be an array.",
				themesExtPoint.name
			));
			return;
		}
571
		iconThemes.forEach(iconTheme => {
M
Martin Aeschlimann 已提交
572
			if (!iconTheme.path || !types.isString(iconTheme.path)) {
M
Martin Aeschlimann 已提交
573 574 575 576
				collector.error(nls.localize(
					'reqpath',
					"Expected string in `contributes.{0}.path`. Provided value: {1}",
					themesExtPoint.name,
577
					String(iconTheme.path)
M
Martin Aeschlimann 已提交
578 579 580
				));
				return;
			}
M
Martin Aeschlimann 已提交
581
			if (!iconTheme.id || !types.isString(iconTheme.id)) {
M
Martin Aeschlimann 已提交
582 583 584 585
				collector.error(nls.localize(
					'reqid',
					"Expected string in `contributes.{0}.id`. Provided value: {1}",
					themesExtPoint.name,
586
					String(iconTheme.path)
M
Martin Aeschlimann 已提交
587 588 589
				));
				return;
			}
590
			let normalizedAbsolutePath = Paths.normalize(Paths.join(extensionFolderPath, iconTheme.path));
M
Martin Aeschlimann 已提交
591

592
			if (normalizedAbsolutePath.indexOf(Paths.normalize(extensionFolderPath)) !== 0) {
M
Martin Aeschlimann 已提交
593 594
				collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", themesExtPoint.name, normalizedAbsolutePath, extensionFolderPath));
			}
595

596
			let themeData = {
597
				id: extensionData.extensionId + '-' + iconTheme.id,
598
				label: iconTheme.label || Paths.basename(iconTheme.path),
M
Martin Aeschlimann 已提交
599
				settingsId: iconTheme.id,
600
				description: iconTheme.description,
M
Martin Aeschlimann 已提交
601
				path: normalizedAbsolutePath,
M
Martin Aeschlimann 已提交
602 603
				extensionData: extensionData,
				isLoaded: false
604 605 606
			};
			this.knownIconThemes.push(themeData);

607 608
			iconThemeSettingSchema.enum.push(themeData.settingsId);
			iconThemeSettingSchema.enumDescriptions.push(themeData.description || '');
M
Martin Aeschlimann 已提交
609 610 611
		});
	}

K
katainaka0503 已提交
612
	private themeExtensionsActivated = new Map<string, boolean>();
M
Martin Aeschlimann 已提交
613
	private sendTelemetry(themeId: string, themeData: ExtensionData, themeType: string) {
614 615 616 617 618 619 620 621 622 623 624 625
		if (themeData) {
			let key = themeType + themeData.extensionId;
			if (!this.themeExtensionsActivated.get(key)) {
				this.telemetryService.publicLog('activatePlugin', {
					id: themeData.extensionId,
					name: themeData.extensionName,
					isBuiltin: themeData.extensionIsBuiltin,
					publisherDisplayName: themeData.extensionPublisher,
					themeId: themeId
				});
				this.themeExtensionsActivated.set(key, true);
			}
626 627
		}
	}
M
Martin Aeschlimann 已提交
628

M
Martin Aeschlimann 已提交
629
	public getFileIconThemes(): TPromise<IFileIconTheme[]> {
M
Martin Aeschlimann 已提交
630
		return this.extensionService.onReady().then(isReady => {
631
			return this.knownIconThemes;
M
Martin Aeschlimann 已提交
632 633 634
		});
	}

635
	public getFileIconTheme() {
M
Martin Aeschlimann 已提交
636
		return this.currentIconTheme;
637 638
	}

M
Martin Aeschlimann 已提交
639
	public setFileIconTheme(iconTheme: string, settingsTarget: ConfigurationTarget): TPromise<IFileIconTheme> {
640
		iconTheme = iconTheme || '';
M
Martin Aeschlimann 已提交
641
		if (iconTheme === this.currentIconTheme.id && this.currentIconTheme.isLoaded) {
M
Martin Aeschlimann 已提交
642
			return this.writeFileIconConfiguration(settingsTarget);
643
		}
M
Martin Aeschlimann 已提交
644 645 646 647 648 649
		let onApply = (newIconTheme: IInternalIconThemeData) => {
			if (newIconTheme) {
				this.currentIconTheme = newIconTheme;
			} else {
				this.currentIconTheme = noFileIconTheme;
			}
M
Martin Aeschlimann 已提交
650

651 652 653 654 655 656 657
			if (this.container) {
				if (newIconTheme) {
					$(this.container).addClass(fileIconsEnabledClass);
				} else {
					$(this.container).removeClass(fileIconsEnabledClass);
				}
			}
M
Martin Aeschlimann 已提交
658
			if (newIconTheme) {
M
Martin Aeschlimann 已提交
659
				this.sendTelemetry(newIconTheme.id, newIconTheme.extensionData, 'fileIcon');
660
			}
M
Martin Aeschlimann 已提交
661
			this.onFileIconThemeChange.fire(this.currentIconTheme);
M
Martin Aeschlimann 已提交
662
			return this.writeFileIconConfiguration(settingsTarget);
663 664
		};

M
Martin Aeschlimann 已提交
665 666 667 668
		return this._findIconThemeData(iconTheme).then(iconThemeData => {
			return _applyIconTheme(iconThemeData, onApply);
		});
	}
M
Martin Aeschlimann 已提交
669

M
Martin Aeschlimann 已提交
670
	private writeFileIconConfiguration(settingsTarget: ConfigurationTarget): TPromise<IFileIconTheme> {
671
		if (!types.isUndefinedOrNull(settingsTarget)) {
672
			return this.configurationWriter.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then(_ => this.currentIconTheme);
M
Martin Aeschlimann 已提交
673 674
		}
		return TPromise.as(this.currentIconTheme);
M
Martin Aeschlimann 已提交
675 676
	}

677 678 679 680
	private get configurationWriter(): ConfigurationWriter {
		// separate out the ConfigurationWriter to avoid a dependency of the IConfigurationEditingService
		if (!this._configurationWriter) {
			this._configurationWriter = this.instantiationService.createInstance(ConfigurationWriter);
681
		}
682
		return this._configurationWriter;
683 684
	}

M
Martin Aeschlimann 已提交
685
	private _findIconThemeData(iconTheme: string): TPromise<IInternalIconThemeData> {
686
		return this.getFileIconThemes().then(allIconSets => {
687
			for (let iconSet of allIconSets) {
M
Martin Aeschlimann 已提交
688
				if (iconSet.id === iconTheme) {
M
Martin Aeschlimann 已提交
689
					return <IInternalIconThemeData>iconSet;
690 691
				}
			}
M
Martin Aeschlimann 已提交
692 693 694 695 696 697 698 699 700 701 702 703
			return null;
		});
	}

	private findIconThemeBySettingsId(settingsId: string): TPromise<IFileIconTheme> {
		return this.getFileIconThemes().then(allIconSets => {
			for (let iconSet of allIconSets) {
				if (iconSet.settingsId === settingsId) {
					return iconSet;
				}
			}
			return null;
M
Martin Aeschlimann 已提交
704 705
		});
	}
E
Erich Gamma 已提交
706 707
}

M
Martin Aeschlimann 已提交
708
function _applyIconTheme(data: IInternalIconThemeData, onApply: (theme: IInternalIconThemeData) => TPromise<IFileIconTheme>): TPromise<IFileIconTheme> {
709
	if (!data) {
710
		_applyRules('', iconThemeRulesClassName);
M
Martin Aeschlimann 已提交
711
		return TPromise.as(onApply(data));
712 713
	}

M
Martin Aeschlimann 已提交
714
	if (data.styleSheetContent) {
715
		_applyRules(data.styleSheetContent, iconThemeRulesClassName);
M
Martin Aeschlimann 已提交
716
		return TPromise.as(onApply(data));
M
Martin Aeschlimann 已提交
717
	}
718
	return _loadIconThemeDocument(data.path).then(iconThemeDocument => {
M
Martin Aeschlimann 已提交
719 720 721 722
		let result = _processIconThemeDocument(data.id, data.path, iconThemeDocument);
		data.styleSheetContent = result.content;
		data.hasFileIcons = result.hasFileIcons;
		data.hasFolderIcons = result.hasFolderIcons;
723
		data.isLoaded = true;
M
Martin Aeschlimann 已提交
724 725
		_applyRules(data.styleSheetContent, iconThemeRulesClassName);
		return onApply(data);
M
Martin Aeschlimann 已提交
726
	}, error => {
727
		return TPromise.wrapError(nls.localize('error.cannotloadicontheme', "Unable to load {0}", data.path));
M
Martin Aeschlimann 已提交
728 729 730
	});
}

J
Johannes Rieken 已提交
731
function _loadIconThemeDocument(fileSetPath: string): TPromise<IconThemeDocument> {
M
Martin Aeschlimann 已提交
732 733
	return pfs.readFile(fileSetPath).then(content => {
		let errors: Json.ParseError[] = [];
M
Martin Aeschlimann 已提交
734
		let contentValue = Json.parse(content.toString(), errors);
M
Martin Aeschlimann 已提交
735
		if (errors.length > 0) {
736
			return TPromise.wrapError(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing file icons file: {0}", errors.map(e => Json.getParseErrorMessage(e.error)).join(', '))));
M
Martin Aeschlimann 已提交
737 738 739 740 741
		}
		return TPromise.as(contentValue);
	});
}

M
Martin Aeschlimann 已提交
742
function _processIconThemeDocument(id: string, iconThemeDocumentPath: string, iconThemeDocument: IconThemeDocument): { content: string; hasFileIcons: boolean; hasFolderIcons: boolean; } {
743

M
Martin Aeschlimann 已提交
744
	let result = { content: '', hasFileIcons: false, hasFolderIcons: false };
745

746
	if (!iconThemeDocument.iconDefinitions) {
M
Martin Aeschlimann 已提交
747
		return result;
M
Martin Aeschlimann 已提交
748
	}
J
Johannes Rieken 已提交
749
	let selectorByDefinitionId: { [def: string]: string[] } = {};
750

751
	function resolvePath(path: string) {
752
		return Paths.join(Paths.dirname(iconThemeDocumentPath), path);
753 754
	}

755
	function collectSelectors(associations: IconsAssociation, baseThemeClassName?: string) {
756 757 758 759 760 761 762
		function addSelector(selector: string, defId: string) {
			if (defId) {
				let list = selectorByDefinitionId[defId];
				if (!list) {
					list = selectorByDefinitionId[defId] = [];
				}
				list.push(selector);
M
Martin Aeschlimann 已提交
763 764
			}
		}
765 766 767 768 769 770
		if (associations) {
			let qualifier = '.show-file-icons';
			if (baseThemeClassName) {
				qualifier = baseThemeClassName + ' ' + qualifier;
			}

771 772
			let expanded = '.monaco-tree-row.expanded'; // workaround for #11453

M
Martin Aeschlimann 已提交
773 774 775 776 777 778 779 780 781
			if (associations.folder) {
				addSelector(`${qualifier} .folder-icon::before`, associations.folder);
				result.hasFolderIcons = true;
			}

			if (associations.folderExpanded) {
				addSelector(`${qualifier} ${expanded} .folder-icon::before`, associations.folderExpanded);
				result.hasFolderIcons = true;
			}
782

M
Martin Aeschlimann 已提交
783 784 785
			if (associations.file) {
				addSelector(`${qualifier} .file-icon::before`, associations.file);
				result.hasFileIcons = true;
786 787
			}

788 789 790 791
			let folderNames = associations.folderNames;
			if (folderNames) {
				for (let folderName in folderNames) {
					addSelector(`${qualifier} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNames[folderName]);
M
Martin Aeschlimann 已提交
792
					result.hasFolderIcons = true;
793 794
				}
			}
795 796 797
			let folderNamesExpanded = associations.folderNamesExpanded;
			if (folderNamesExpanded) {
				for (let folderName in folderNamesExpanded) {
798
					addSelector(`${qualifier} ${expanded} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNamesExpanded[folderName]);
M
Martin Aeschlimann 已提交
799
					result.hasFolderIcons = true;
800 801
				}
			}
802

803 804 805 806
			let languageIds = associations.languageIds;
			if (languageIds) {
				for (let languageId in languageIds) {
					addSelector(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`, languageIds[languageId]);
M
Martin Aeschlimann 已提交
807
					result.hasFileIcons = true;
808 809
				}
			}
810 811 812
			let fileExtensions = associations.fileExtensions;
			if (fileExtensions) {
				for (let fileExtension in fileExtensions) {
B
Benjamin Pasero 已提交
813
					let selectors: string[] = [];
814 815 816 817 818
					let segments = fileExtension.toLowerCase().split('.');
					for (let i = 0; i < segments.length; i++) {
						selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`);
					}
					addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileExtensions[fileExtension]);
M
Martin Aeschlimann 已提交
819
					result.hasFileIcons = true;
820 821 822 823 824
				}
			}
			let fileNames = associations.fileNames;
			if (fileNames) {
				for (let fileName in fileNames) {
B
Benjamin Pasero 已提交
825
					let selectors: string[] = [];
M
Martin Aeschlimann 已提交
826 827 828
					fileName = fileName.toLowerCase();
					selectors.push(`.${escapeCSS(fileName)}-name-file-icon`);
					let segments = fileName.split('.');
829 830 831 832
					for (let i = 1; i < segments.length; i++) {
						selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`);
					}
					addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileNames[fileName]);
M
Martin Aeschlimann 已提交
833
					result.hasFileIcons = true;
834 835
				}
			}
M
Martin Aeschlimann 已提交
836 837
		}
	}
838 839
	collectSelectors(iconThemeDocument);
	collectSelectors(iconThemeDocument.light, '.vs');
840
	collectSelectors(iconThemeDocument.highContrast, '.hc-black');
841

M
Martin Aeschlimann 已提交
842 843
	if (!result.hasFileIcons && !result.hasFolderIcons) {
		return result;
844 845
	}

M
Martin Aeschlimann 已提交
846
	let cssRules: string[] = [];
847

848
	let fonts = iconThemeDocument.fonts;
849 850 851 852 853 854
	if (Array.isArray(fonts)) {
		fonts.forEach(font => {
			let src = font.src.map(l => `url('${resolvePath(l.path)}') format('${l.format}')`).join(', ');
			cssRules.push(`@font-face { src: ${src}; font-family: '${font.id}'; font-weigth: ${font.weight}; font-style: ${font.style}; }`);
		});
		cssRules.push(`.show-file-icons .file-icon::before, .show-file-icons .folder-icon::before { font-family: '${fonts[0].id}'; font-size: ${fonts[0].size || '150%'}}`);
855 856
	}

M
Martin Aeschlimann 已提交
857 858
	for (let defId in selectorByDefinitionId) {
		let selectors = selectorByDefinitionId[defId];
859
		let definition = iconThemeDocument.iconDefinitions[defId];
M
Martin Aeschlimann 已提交
860 861
		if (definition) {
			if (definition.iconPath) {
862 863
				cssRules.push(`${selectors.join(', ')} { content: ' '; background-image: url("${resolvePath(definition.iconPath)}"); }`);
			}
864
			if (definition.fontCharacter || definition.fontColor) {
865
				let body = '';
866 867
				if (definition.fontColor) {
					body += ` color: ${definition.fontColor};`;
868
				}
869 870
				if (definition.fontCharacter) {
					body += ` content: '${definition.fontCharacter}';`;
871 872 873 874
				}
				if (definition.fontSize) {
					body += ` font-size: ${definition.fontSize};`;
				}
875 876 877
				if (definition.fontId) {
					body += ` font-family: ${definition.fontId};`;
				}
878
				cssRules.push(`${selectors.join(', ')} { ${body} }`);
M
Martin Aeschlimann 已提交
879 880 881
			}
		}
	}
M
Martin Aeschlimann 已提交
882 883
	result.content = cssRules.join('\n');
	return result;
M
Martin Aeschlimann 已提交
884
}
885

886 887 888 889
function escapeCSS(str: string) {
	return window['CSS'].escape(str);
}

M
Martin Aeschlimann 已提交
890
let colorThemeRulesClassName = 'contributedColorTheme';
891
let iconThemeRulesClassName = 'contributedIconTheme';
E
Erich Gamma 已提交
892

M
Martin Aeschlimann 已提交
893 894
function _applyRules(styleSheetContent: string, rulesClassName: string) {
	let themeStyles = document.head.getElementsByClassName(rulesClassName);
E
Erich Gamma 已提交
895
	if (themeStyles.length === 0) {
B
Benjamin Pasero 已提交
896 897
		let elStyle = document.createElement('style');
		elStyle.type = 'text/css';
M
Martin Aeschlimann 已提交
898
		elStyle.className = rulesClassName;
E
Erich Gamma 已提交
899 900 901
		elStyle.innerHTML = styleSheetContent;
		document.head.appendChild(elStyle);
	} else {
B
Benjamin Pasero 已提交
902
		(<HTMLStyleElement>themeStyles[0]).innerHTML = styleSheetContent;
E
Erich Gamma 已提交
903 904 905
	}
}

906 907
colorThemeSchema.register();
fileIconThemeSchema.register();
M
Martin Aeschlimann 已提交
908

909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
class ConfigurationWriter {
	constructor( @IConfigurationService private configurationService: IConfigurationService, @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService) {
	}

	public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget): TPromise<any> {
		let settings = this.configurationService.lookup(key);
		if (settingsTarget === ConfigurationTarget.USER) {
			if (value === settings.user) {
				return TPromise.as(null); // nothing to do
			} else if (value === settings.default) {
				if (types.isUndefined(settings.user)) {
					return TPromise.as(null); // nothing to do
				}
				value = void 0; // remove configuration from user settings
			}
		} else if (settingsTarget === ConfigurationTarget.WORKSPACE) {
			if (value === settings.value) {
				return TPromise.as(null); // nothing to do
			}
		}
		return this.configurationEditingService.writeConfiguration(settingsTarget, { key, value });
	}
}

933
// Configuration: Themes
M
Martin Aeschlimann 已提交
934
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
935

936
const colorThemeSettingSchema: IJSONSchema = {
937 938
	type: 'string',
	description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."),
939
	default: DEFAULT_THEME_SETTING_VALUE,
940 941
	enum: [CUSTOM_THEME_ID],
	enumDescriptions: [nls.localize('customThemeDescription', "Custom theme as defined in '{0}'", CUSTOM_COLOR_THEME_SETTING)],
942 943
	errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."),
};
944
const iconThemeSettingSchema: IJSONSchema = {
945 946 947 948 949 950 951
	type: ['string', 'null'],
	default: null,
	description: nls.localize('iconTheme', "Specifies the icon theme used in the workbench."),
	enum: [null],
	enumDescriptions: [nls.localize('noIconThemeDesc', 'No file icons')],
	errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.")
};
952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974
const extendsSchema: IJSONSchema = {
	description: nls.localize('customTheme.extends', "theme to extend. This will inherit color and token colors from the selected theme."),
	type: 'string',
	enum: [],
	enumDescriptions: []
};
const userThemeSettingSchema: IJSONSchema = {
	type: 'object',
	description: nls.localize('customTheme', "Custom theme."),
	properties: {
		id: {
			type: 'string'
		},
		type: {
			enum: ['light', 'dark', 'hc']
		},
		extends: extendsSchema,
		colors: colorThemeSchema.colorsSchema,
		tokenColors: colorThemeSchema.tokenColorsSchema
	},
	required: ['type']
};

M
Martin Aeschlimann 已提交
975
configurationRegistry.registerConfiguration({
976 977 978 979
	id: 'workbench',
	order: 7.1,
	type: 'object',
	properties: {
980 981 982
		[COLOR_THEME_SETTING]: colorThemeSettingSchema,
		[ICON_THEME_SETTING]: iconThemeSettingSchema,
		[CUSTOM_COLOR_THEME_SETTING]: userThemeSettingSchema
M
Martin Aeschlimann 已提交
983
	}
984 985
});