workbenchThemeService.ts 35.6 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';
J
Johannes Rieken 已提交
8
import URI from 'vs/base/common/uri';
E
Erich Gamma 已提交
9
import nls = require('vs/nls');
10
import * as Paths from 'path';
11
import Json = require('vs/base/common/json');
M
Martin Aeschlimann 已提交
12
import * as types from 'vs/base/common/types';
13
import * as objects from 'vs/base/common/objects';
J
Johannes Rieken 已提交
14 15
import { IThemeExtensionPoint } from 'vs/platform/theme/common/themeExtensionPoint';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
16
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry';
17
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';
18
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
J
Johannes Rieken 已提交
19 20 21 22
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 已提交
23
import errors = require('vs/base/common/errors');
M
Martin Aeschlimann 已提交
24 25 26
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';
27 28
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IMessageService } from 'vs/platform/message/common/message';
29
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
30
import Severity from 'vs/base/common/severity';
31
import { ColorThemeData, fromStorageData, fromUserTheme, fromExtensionTheme } from './colorThemeData';
32
import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService';
33
import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
J
Johannes Rieken 已提交
34 35 36

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

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

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

44

E
Erich Gamma 已提交
45 46
// implementation

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

50 51
const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData';

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

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

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

J
Johannes Rieken 已提交
59
function validateThemeId(theme: string): string {
60 61
	// migrations
	switch (theme) {
62 63 64
		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`;
65 66 67 68 69 70
		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 已提交
71
let themesExtPoint = ExtensionsRegistry.registerExtensionPoint<IThemeExtensionPoint[]>('themes', [], {
E
Erich Gamma 已提交
72 73 74 75
	description: nls.localize('vscode.extension.contributes.themes', 'Contributes textmate color themes.'),
	type: 'array',
	items: {
		type: 'object',
M
Martin Aeschlimann 已提交
76
		defaultSnippets: [{ body: { label: '${1:label}', id: '${2:id}', uiTheme: VS_DARK_THEME, path: './themes/${3:id}.tmTheme.' } }],
E
Erich Gamma 已提交
77
		properties: {
M
Martin Aeschlimann 已提交
78 79 80 81
			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 已提交
82 83 84 85 86
			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 已提交
87
				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.'),
88
				enum: [VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME]
E
Erich Gamma 已提交
89 90 91 92 93
			},
			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 已提交
94 95 96 97 98
		},
		required: ['path', 'uiTheme']
	}
});

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

M
Martin Aeschlimann 已提交
225 226 227
		this.currentIconTheme = {
			id: '',
			label: '',
M
Martin Aeschlimann 已提交
228
			settingsId: null,
M
Martin Aeschlimann 已提交
229 230
			isLoaded: false,
			hasFileIcons: false,
M
Martin Aeschlimann 已提交
231 232
			hasFolderIcons: false,
			extensionData: null
M
Martin Aeschlimann 已提交
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 259

		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 已提交
260 261 262

		themesExtPoint.setHandler((extensions) => {
			for (let ext of extensions) {
263 264 265 266 267 268 269
				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 已提交
270 271
			}
		});
M
Martin Aeschlimann 已提交
272

273
		iconThemeExtPoint.setHandler((extensions) => {
M
Martin Aeschlimann 已提交
274
			for (let ext of extensions) {
275 276 277 278 279 280 281
				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 已提交
282 283 284
			}
		});

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

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

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

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

304
	private backupSettings(): TPromise<string> {
305 306 307 308 309
		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 => {
310 311
			if (err && err.code === 'ENOENT') {
				return TPromise.as(null); // ignore, user config file doesn't exist yet
312
			};
313 314 315 316
			return TPromise.wrapError(err);
		});
	}

317 318 319
	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);
320 321
		if (types.isUndefined(legacyColorThemeId) && types.isUndefined(legacyIconThemeId)) {
			return TPromise.as(null);
M
Martin Aeschlimann 已提交
322
		}
323 324 325 326 327 328
		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;
329
					return this.configurationWriter.writeConfiguration(COLOR_THEME_SETTING, value, ConfigurationTarget.USER).then(null, error => null);
330 331 332 333 334 335 336
				});
			}
			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;
337
						return this.configurationWriter.writeConfiguration(ICON_THEME_SETTING, value, ConfigurationTarget.USER).then(null, error => null);
338 339 340 341 342 343 344 345 346 347 348
					});
				});
			}
			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);
				}
			});
		});
349
	}
M
Martin Aeschlimann 已提交
350

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

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

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

361
		return Promise.join([
M
Martin Aeschlimann 已提交
362 363 364 365 366 367
			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);
			}),
368
		]);
M
Martin Aeschlimann 已提交
369 370
	}

371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
	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);
				});
			}
388 389 390

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

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

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

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

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

M
Martin Aeschlimann 已提交
413 414
		return this.findThemeData(themeId, DEFAULT_THEME_ID).then(themeData => {
			if (themeData) {
415
				return themeData.ensureLoaded(this).then(_ => {
416 417 418 419 420
					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);
					}
421
					this.updateDynamicCSSRules(themeData);
422
					return this.applyTheme(themeData, settingsTarget);
423 424 425
				}, error => {
					return TPromise.wrapError(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.path, error.message));
				});
M
Martin Aeschlimann 已提交
426 427 428 429 430
			}
			return null;
		});
	}

431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
	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);
	}

446 447 448 449 450 451 452 453 454 455
	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));

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

458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
		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);
	};

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

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

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

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

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

520 521 522 523 524 525 526 527 528
	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);
			}
		}
	}

529
	private onThemes(extensionFolderPath: string, extensionData: ExtensionData, themes: IThemeExtensionPoint[], collector: ExtensionMessageCollector): void {
E
Erich Gamma 已提交
530 531 532 533 534 535 536 537 538
		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 已提交
539
			if (!theme.path || !types.isString(theme.path)) {
E
Erich Gamma 已提交
540 541 542 543 544 545 546 547 548 549
				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));

550
			if (normalizedAbsolutePath.indexOf(Paths.normalize(extensionFolderPath)) !== 0) {
E
Erich Gamma 已提交
551 552
				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));
			}
553 554
			let themeData = fromExtensionTheme(theme, normalizedAbsolutePath, extensionData);
			this.extensionsColorThemes.push(themeData);
E
Erich Gamma 已提交
555

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

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

593
			if (normalizedAbsolutePath.indexOf(Paths.normalize(extensionFolderPath)) !== 0) {
M
Martin Aeschlimann 已提交
594 595
				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));
			}
596

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

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

K
katainaka0503 已提交
613
	private themeExtensionsActivated = new Map<string, boolean>();
M
Martin Aeschlimann 已提交
614
	private sendTelemetry(themeId: string, themeData: ExtensionData, themeType: string) {
615 616 617 618 619 620 621 622 623 624 625 626
		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);
			}
627 628
		}
	}
M
Martin Aeschlimann 已提交
629

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

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

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

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

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

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

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

M
Martin Aeschlimann 已提交
686
	private _findIconThemeData(iconTheme: string): TPromise<IInternalIconThemeData> {
687
		return this.getFileIconThemes().then(allIconSets => {
688
			for (let iconSet of allIconSets) {
M
Martin Aeschlimann 已提交
689
				if (iconSet.id === iconTheme) {
M
Martin Aeschlimann 已提交
690
					return <IInternalIconThemeData>iconSet;
691 692
				}
			}
M
Martin Aeschlimann 已提交
693 694 695 696 697 698 699 700 701 702 703 704
			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 已提交
705 706
		});
	}
E
Erich Gamma 已提交
707 708
}

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

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

J
Johannes Rieken 已提交
732
function _loadIconThemeDocument(fileSetPath: string): TPromise<IconThemeDocument> {
M
Martin Aeschlimann 已提交
733 734
	return pfs.readFile(fileSetPath).then(content => {
		let errors: Json.ParseError[] = [];
M
Martin Aeschlimann 已提交
735
		let contentValue = Json.parse(content.toString(), errors);
M
Martin Aeschlimann 已提交
736
		if (errors.length > 0) {
737
			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 已提交
738 739 740 741 742
		}
		return TPromise.as(contentValue);
	});
}

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

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

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

752
	function resolvePath(path: string) {
J
Johannes Rieken 已提交
753
		const uri = URI.file(Paths.join(Paths.dirname(iconThemeDocumentPath), path));
754
		return uri.toString();
755 756
	}

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

773 774
			let expanded = '.monaco-tree-row.expanded'; // workaround for #11453

M
Martin Aeschlimann 已提交
775 776 777 778 779 780 781 782 783
			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;
			}
784

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

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

805 806 807 808
			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 已提交
809
					result.hasFileIcons = true;
810 811
				}
			}
812 813 814
			let fileExtensions = associations.fileExtensions;
			if (fileExtensions) {
				for (let fileExtension in fileExtensions) {
B
Benjamin Pasero 已提交
815
					let selectors: string[] = [];
816 817 818 819 820
					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 已提交
821
					result.hasFileIcons = true;
822 823 824 825 826
				}
			}
			let fileNames = associations.fileNames;
			if (fileNames) {
				for (let fileName in fileNames) {
B
Benjamin Pasero 已提交
827
					let selectors: string[] = [];
M
Martin Aeschlimann 已提交
828 829 830
					fileName = fileName.toLowerCase();
					selectors.push(`.${escapeCSS(fileName)}-name-file-icon`);
					let segments = fileName.split('.');
831 832 833 834
					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 已提交
835
					result.hasFileIcons = true;
836 837
				}
			}
M
Martin Aeschlimann 已提交
838 839
		}
	}
840 841
	collectSelectors(iconThemeDocument);
	collectSelectors(iconThemeDocument.light, '.vs');
842
	collectSelectors(iconThemeDocument.highContrast, '.hc-black');
843

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

M
Martin Aeschlimann 已提交
848
	let cssRules: string[] = [];
849

850
	let fonts = iconThemeDocument.fonts;
851 852 853 854 855 856
	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%'}}`);
857 858
	}

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

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

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

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

908 909
colorThemeSchema.register();
fileIconThemeSchema.register();
M
Martin Aeschlimann 已提交
910

911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934
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 });
	}
}

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

938
const colorThemeSettingSchema: IJSONSchema = {
939 940
	type: 'string',
	description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."),
941
	default: DEFAULT_THEME_SETTING_VALUE,
942 943
	enum: [CUSTOM_THEME_ID],
	enumDescriptions: [nls.localize('customThemeDescription', "Custom theme as defined in '{0}'", CUSTOM_COLOR_THEME_SETTING)],
944 945
	errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."),
};
946
const iconThemeSettingSchema: IJSONSchema = {
947 948 949 950 951 952 953
	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.")
};
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
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 已提交
977
configurationRegistry.registerConfiguration({
978 979 980 981
	id: 'workbench',
	order: 7.1,
	type: 'object',
	properties: {
982 983 984
		[COLOR_THEME_SETTING]: colorThemeSettingSchema,
		[ICON_THEME_SETTING]: iconThemeSettingSchema,
		[CUSTOM_COLOR_THEME_SETTING]: userThemeSettingSchema
M
Martin Aeschlimann 已提交
985
	}
986 987
});