workbenchThemeService.ts 36.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';
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
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
15
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry';
16
import { IWorkbenchThemeService, IColorTheme, IFileIconTheme, ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_COLORS_SETTING, DEPRECATED_CUSTOM_COLORS_SETTING } from 'vs/workbench/services/themes/common/workbenchThemeService';
17
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
J
Johannes Rieken 已提交
18 19
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
20
import { Registry } from 'vs/platform/registry/common/platform';
J
Johannes Rieken 已提交
21
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, fromExtensionTheme, createUnloadedTheme } from './colorThemeData';
31
import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService';
32
import { editorBackground } 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
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
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
const DEFAULT_ICON_THEME_SETTING_VALUE = 'vs-seti';
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
			label: {
				description: nls.localize('vscode.extension.contributes.themes.label', 'Label of the color theme as shown in the UI.'),
				type: 'string'
			},
86 87 88 89
			uiTheme: {
				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.'),
				enum: [VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME]
			},
E
Erich Gamma 已提交
90
			path: {
91
				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\'.'),
E
Erich Gamma 已提交
92 93
				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
export interface IColorCustomizations {
171
	[colorId: string]: string;
172 173
}

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

184
export class WorkbenchThemeService implements IWorkbenchThemeService {
185
	_serviceBrand: any;
E
Erich Gamma 已提交
186

187
	private extensionsColorThemes: ColorThemeData[];
188
	private colorCustomizations: IColorCustomizations;
189
	private numberOfColorCustomizations: number;
190
	private currentColorTheme: ColorThemeData;
M
Martin Aeschlimann 已提交
191
	private container: HTMLElement;
M
Martin Aeschlimann 已提交
192
	private onColorThemeChange: Emitter<IColorTheme>;
M
Martin Aeschlimann 已提交
193

M
Martin Aeschlimann 已提交
194 195 196
	private knownIconThemes: IInternalIconThemeData[];
	private currentIconTheme: IFileIconTheme;
	private onFileIconThemeChange: Emitter<IFileIconTheme>;
E
Erich Gamma 已提交
197

198
	private themingParticipantChangeListener: IDisposable;
199
	private _configurationWriter: ConfigurationWriter;
200

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

B
Benjamin Pasero 已提交
212
		this.container = container;
213
		this.extensionsColorThemes = [];
214 215
		this.colorCustomizations = {};
		this.numberOfColorCustomizations = 0;
216
		this.onFileIconThemeChange = new Emitter<IFileIconTheme>();
217
		this.knownIconThemes = [];
218 219
		this.onColorThemeChange = new Emitter<IColorTheme>();

M
Martin Aeschlimann 已提交
220 221 222
		this.currentIconTheme = {
			id: '',
			label: '',
M
Martin Aeschlimann 已提交
223
			settingsId: null,
M
Martin Aeschlimann 已提交
224 225
			isLoaded: false,
			hasFileIcons: false,
M
Martin Aeschlimann 已提交
226 227
			hasFolderIcons: false,
			extensionData: null
M
Martin Aeschlimann 已提交
228
		};
229

230 231
		this.updateColorCustomizations(false);

232 233 234
		// 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
235
		let themeData = null;
236 237
		let persistedThemeData = this.storageService.get(PERSISTED_THEME_STORAGE_KEY);
		if (persistedThemeData) {
238
			themeData = fromStorageData(persistedThemeData);
239
		}
240
		if (!themeData) {
241
			let isLightTheme = (Array.prototype.indexOf.call(document.body.classList, 'vs') >= 0);
242
			themeData = createUnloadedTheme(isLightTheme ? VS_LIGHT_THEME : VS_DARK_THEME);
243
		}
244 245 246
		themeData.setCustomColors(this.colorCustomizations);
		this.updateDynamicCSSRules(themeData);
		this.applyTheme(themeData, null, true);
E
Erich Gamma 已提交
247 248 249

		themesExtPoint.setHandler((extensions) => {
			for (let ext of extensions) {
250 251 252 253 254 255 256
				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 已提交
257 258
			}
		});
M
Martin Aeschlimann 已提交
259

260
		iconThemeExtPoint.setHandler((extensions) => {
M
Martin Aeschlimann 已提交
261
			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.onIconThemes(ext.description.extensionFolderPath, extensionData, ext.value, ext.collector);
M
Martin Aeschlimann 已提交
269 270 271
			}
		});

272 273 274
		this.migrate().then(_ => {
			this.initialize().then(null, errors.onUnexpectedError).then(_ => {
				this.installConfigurationListener();
275
			});
M
Martin Aeschlimann 已提交
276
		});
M
Martin Aeschlimann 已提交
277 278
	}

M
Martin Aeschlimann 已提交
279
	public get onDidColorThemeChange(): Event<IColorTheme> {
M
Martin Aeschlimann 已提交
280
		return this.onColorThemeChange.event;
E
Erich Gamma 已提交
281 282
	}

M
Martin Aeschlimann 已提交
283 284 285 286
	public get onDidFileIconThemeChange(): Event<IFileIconTheme> {
		return this.onFileIconThemeChange.event;
	}

287 288
	public get onThemeChange(): Event<ITheme> {
		return this.onColorThemeChange.event;
289 290
	}

291
	private backupSettings(): TPromise<string> {
292 293 294 295 296
		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 => {
297
			if (err && err.code === 'ENOENT') {
298
				return TPromise.as<string>(null); // ignore, user config file doesn't exist yet
299
			};
300
			return TPromise.wrapError<string>(err);
301 302 303
		});
	}

B
Benjamin Pasero 已提交
304
	private migrate(): TPromise<void> {
305 306
		let legacyColorThemeId = this.storageService.get('workbench.theme', StorageScope.GLOBAL, void 0);
		let legacyIconThemeId = this.storageService.get('workbench.iconTheme', StorageScope.GLOBAL, void 0);
307 308
		if (types.isUndefined(legacyColorThemeId) && types.isUndefined(legacyIconThemeId)) {
			return TPromise.as(null);
M
Martin Aeschlimann 已提交
309
		}
310 311 312 313 314 315
		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;
316
					return this.configurationWriter.writeConfiguration(COLOR_THEME_SETTING, value, ConfigurationTarget.USER).then(null, error => null);
317 318 319 320 321 322 323
				});
			}
			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;
324
						return this.configurationWriter.writeConfiguration(ICON_THEME_SETTING, value, ConfigurationTarget.USER).then(null, error => null);
325 326 327 328 329 330 331 332 333 334 335
					});
				});
			}
			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);
				}
			});
		});
336
	}
M
Martin Aeschlimann 已提交
337

B
Benjamin Pasero 已提交
338
	private initialize(): TPromise<IFileIconTheme> {
339

340
		this.updateColorCustomizations(false);
341

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

345
		return Promise.join([
M
Martin Aeschlimann 已提交
346 347 348 349 350 351
			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);
			}),
352
		]);
M
Martin Aeschlimann 已提交
353 354
	}

355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
	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);
				});
			}
372

373
			this.updateColorCustomizations();
374 375 376
		});
	}

377 378 379 380
	public getTheme(): ITheme {
		return this.getColorTheme();
	}

M
Martin Aeschlimann 已提交
381
	public setColorTheme(themeId: string, settingsTarget: ConfigurationTarget): TPromise<IColorTheme> {
M
Martin Aeschlimann 已提交
382
		if (!themeId) {
M
Martin Aeschlimann 已提交
383
			return TPromise.as(null);
M
Martin Aeschlimann 已提交
384
		}
M
Martin Aeschlimann 已提交
385
		if (themeId === this.currentColorTheme.id && this.currentColorTheme.isLoaded) {
386
			return this.writeColorThemeConfiguration(settingsTarget);
M
Martin Aeschlimann 已提交
387 388 389 390
		}

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

391

M
Martin Aeschlimann 已提交
392 393
		return this.findThemeData(themeId, DEFAULT_THEME_ID).then(themeData => {
			if (themeData) {
394
				return themeData.ensureLoaded(this).then(_ => {
395
					if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) {
396
						// the loaded theme is identical to the perisisted theme. Don't need to send an event.
397
						this.currentColorTheme = themeData;
398
						themeData.setCustomColors(this.colorCustomizations);
399 400
						return TPromise.as(themeData);
					}
401
					themeData.setCustomColors(this.colorCustomizations);
402
					this.updateDynamicCSSRules(themeData);
403
					return this.applyTheme(themeData, settingsTarget);
404
				}, error => {
405
					return TPromise.wrapError<IColorTheme>(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.path, error.message));
406
				});
M
Martin Aeschlimann 已提交
407 408 409 410 411
			}
			return null;
		});
	}

412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
	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);
	}

427
	private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget, silent = false): TPromise<IColorTheme> {
428 429 430
		if (this.container) {
			if (this.currentColorTheme) {
				$(this.container).removeClass(this.currentColorTheme.id);
431 432
			} else {
				$(this.container).removeClass(VS_DARK_THEME, VS_LIGHT_THEME, VS_HC_THEME);
433 434 435 436
			}
			$(this.container).addClass(newTheme.id);
		}
		this.currentColorTheme = newTheme;
437 438 439
		if (!this.themingParticipantChangeListener) {
			this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(p => this.updateDynamicCSSRules(this.currentColorTheme));
		}
440

441 442
		this.sendTelemetry(newTheme.id, newTheme.extensionData, 'color');

443 444 445 446 447 448 449
		if (silent) {
			return TPromise.as(null);
		}

		this.onColorThemeChange.fire(this.currentColorTheme);

		if (settingsTarget !== ConfigurationTarget.WORKSPACE) {
450
			let background = newTheme.getColor(editorBackground).toRGBHex(); // only take RGB, its what is used in the initial CSS
451
			let data = { id: newTheme.id, background: background };
452
			this.windowService.broadcast({ channel: 'vscode:changeColorTheme', payload: JSON.stringify(data) });
453 454 455 456 457 458 459
		}
		// remember theme data for a quick restore
		this.storageService.store(PERSISTED_THEME_STORAGE_KEY, newTheme.toStorageData());

		return this.writeColorThemeConfiguration(settingsTarget);
	};

460
	private writeColorThemeConfiguration(settingsTarget: ConfigurationTarget): TPromise<IColorTheme> {
461
		if (!types.isUndefinedOrNull(settingsTarget)) {
462
			return this.configurationWriter.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then(_ => this.currentColorTheme);
M
Martin Aeschlimann 已提交
463 464
		}
		return TPromise.as(this.currentColorTheme);
M
Martin Aeschlimann 已提交
465 466
	}

467
	public getColorTheme(): IColorTheme {
M
Martin Aeschlimann 已提交
468
		return this.currentColorTheme;
A
Alex Dima 已提交
469 470
	}

M
Martin Aeschlimann 已提交
471
	private findThemeData(themeId: string, defaultId?: string): TPromise<ColorThemeData> {
M
Martin Aeschlimann 已提交
472
		return this.getColorThemes().then(allThemes => {
473
			let defaultTheme: ColorThemeData = void 0;
M
Martin Aeschlimann 已提交
474 475
			for (let t of allThemes) {
				if (t.id === themeId) {
476
					return <ColorThemeData>t;
M
Martin Aeschlimann 已提交
477 478
				}
				if (t.id === defaultId) {
479
					defaultTheme = <ColorThemeData>t;
M
Martin Aeschlimann 已提交
480 481
				}
			}
M
Martin Aeschlimann 已提交
482
			return defaultTheme;
E
Erich Gamma 已提交
483 484 485
		});
	}

486
	public findThemeDataBySettingsId(settingsId: string, defaultId: string): TPromise<ColorThemeData> {
M
Martin Aeschlimann 已提交
487
		return this.getColorThemes().then(allThemes => {
488
			let defaultTheme: ColorThemeData = void 0;
M
Martin Aeschlimann 已提交
489 490
			for (let t of allThemes) {
				if (t.settingsId === settingsId) {
491
					return <ColorThemeData>t;
M
Martin Aeschlimann 已提交
492 493
				}
				if (t.id === defaultId) {
494
					defaultTheme = <ColorThemeData>t;
M
Martin Aeschlimann 已提交
495
				}
E
Erich Gamma 已提交
496
			}
M
Martin Aeschlimann 已提交
497
			return defaultTheme;
B
Benjamin Pasero 已提交
498
		});
E
Erich Gamma 已提交
499 500
	}

M
Martin Aeschlimann 已提交
501
	public getColorThemes(): TPromise<IColorTheme[]> {
A
Alex Dima 已提交
502
		return this.extensionService.onReady().then(isReady => {
503
			return this.extensionsColorThemes;
E
Erich Gamma 已提交
504 505 506
		});
	}

507
	private hasCustomizationChanged(newColorCustomizations: IColorCustomizations, newColorIds: string[]): boolean {
508
		if (newColorIds.length !== this.numberOfColorCustomizations) {
509 510
			return true;
		}
511 512 513
		for (let key of newColorIds) {
			let color = this.colorCustomizations[key];
			if (!color || color !== newColorCustomizations[key]) {
514
				return true;
515 516
			}
		}
517 518 519 520 521 522 523 524 525 526 527 528 529
		return false;
	}

	private updateColorCustomizations(notify = true): void {
		let newColorCustomizations = this.configurationService.lookup<IColorCustomizations>(CUSTOM_COLORS_SETTING).value || {};
		let newColorIds = Object.keys(newColorCustomizations);
		if (newColorIds.length === 0) {
			newColorCustomizations = this.configurationService.lookup<IColorCustomizations>(DEPRECATED_CUSTOM_COLORS_SETTING).value || {};
			newColorIds = Object.keys(newColorCustomizations);
		}
		if (this.hasCustomizationChanged(newColorCustomizations, newColorIds)) {
			this.colorCustomizations = newColorCustomizations;
			this.numberOfColorCustomizations = newColorIds.length;
530 531 532 533 534 535
			if (this.currentColorTheme) {
				this.currentColorTheme.setCustomColors(newColorCustomizations);
				if (notify) {
					this.updateDynamicCSSRules(this.currentColorTheme);
					this.onColorThemeChange.fire(this.currentColorTheme);
				}
536
			}
537 538 539
		}
	}

540
	private onThemes(extensionFolderPath: string, extensionData: ExtensionData, themes: IThemeExtensionPoint[], collector: ExtensionMessageCollector): void {
E
Erich Gamma 已提交
541 542 543 544 545 546 547 548 549
		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 已提交
550
			if (!theme.path || !types.isString(theme.path)) {
E
Erich Gamma 已提交
551 552 553 554 555 556 557 558 559 560
				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));

561
			if (normalizedAbsolutePath.indexOf(Paths.normalize(extensionFolderPath)) !== 0) {
E
Erich Gamma 已提交
562 563
				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));
			}
564
			let themeData = fromExtensionTheme(theme, normalizedAbsolutePath, extensionData);
565
			this.extensionsColorThemes.push(themeData);
E
Erich Gamma 已提交
566

567 568
			colorThemeSettingSchema.enum.push(themeData.settingsId);
			colorThemeSettingSchema.enumDescriptions.push(themeData.description || '');
B
Benjamin Pasero 已提交
569
		});
E
Erich Gamma 已提交
570
	}
571

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

602
			if (normalizedAbsolutePath.indexOf(Paths.normalize(extensionFolderPath)) !== 0) {
M
Martin Aeschlimann 已提交
603 604
				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));
			}
605

606
			let themeData = {
607
				id: extensionData.extensionId + '-' + iconTheme.id,
608
				label: iconTheme.label || Paths.basename(iconTheme.path),
M
Martin Aeschlimann 已提交
609
				settingsId: iconTheme.id,
610
				description: iconTheme.description,
M
Martin Aeschlimann 已提交
611
				path: normalizedAbsolutePath,
M
Martin Aeschlimann 已提交
612 613
				extensionData: extensionData,
				isLoaded: false
614 615 616
			};
			this.knownIconThemes.push(themeData);

617 618
			iconThemeSettingSchema.enum.push(themeData.settingsId);
			iconThemeSettingSchema.enumDescriptions.push(themeData.description || '');
M
Martin Aeschlimann 已提交
619 620 621
		});
	}

K
katainaka0503 已提交
622
	private themeExtensionsActivated = new Map<string, boolean>();
M
Martin Aeschlimann 已提交
623
	private sendTelemetry(themeId: string, themeData: ExtensionData, themeType: string) {
624 625 626 627 628 629 630 631 632 633 634 635
		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);
			}
636 637
		}
	}
M
Martin Aeschlimann 已提交
638

M
Martin Aeschlimann 已提交
639
	public getFileIconThemes(): TPromise<IFileIconTheme[]> {
M
Martin Aeschlimann 已提交
640
		return this.extensionService.onReady().then(isReady => {
641
			return this.knownIconThemes;
M
Martin Aeschlimann 已提交
642 643 644
		});
	}

645
	public getFileIconTheme() {
M
Martin Aeschlimann 已提交
646
		return this.currentIconTheme;
647 648
	}

M
Martin Aeschlimann 已提交
649
	public setFileIconTheme(iconTheme: string, settingsTarget: ConfigurationTarget): TPromise<IFileIconTheme> {
650
		iconTheme = iconTheme || '';
M
Martin Aeschlimann 已提交
651
		if (iconTheme === this.currentIconTheme.id && this.currentIconTheme.isLoaded) {
M
Martin Aeschlimann 已提交
652
			return this.writeFileIconConfiguration(settingsTarget);
653
		}
M
Martin Aeschlimann 已提交
654 655 656 657 658 659
		let onApply = (newIconTheme: IInternalIconThemeData) => {
			if (newIconTheme) {
				this.currentIconTheme = newIconTheme;
			} else {
				this.currentIconTheme = noFileIconTheme;
			}
M
Martin Aeschlimann 已提交
660

661 662 663 664 665 666 667
			if (this.container) {
				if (newIconTheme) {
					$(this.container).addClass(fileIconsEnabledClass);
				} else {
					$(this.container).removeClass(fileIconsEnabledClass);
				}
			}
M
Martin Aeschlimann 已提交
668
			if (newIconTheme) {
M
Martin Aeschlimann 已提交
669
				this.sendTelemetry(newIconTheme.id, newIconTheme.extensionData, 'fileIcon');
670
			}
M
Martin Aeschlimann 已提交
671
			this.onFileIconThemeChange.fire(this.currentIconTheme);
M
Martin Aeschlimann 已提交
672
			return this.writeFileIconConfiguration(settingsTarget);
673 674
		};

M
Martin Aeschlimann 已提交
675 676 677 678
		return this._findIconThemeData(iconTheme).then(iconThemeData => {
			return _applyIconTheme(iconThemeData, onApply);
		});
	}
M
Martin Aeschlimann 已提交
679

M
Martin Aeschlimann 已提交
680
	private writeFileIconConfiguration(settingsTarget: ConfigurationTarget): TPromise<IFileIconTheme> {
681
		if (!types.isUndefinedOrNull(settingsTarget)) {
682
			return this.configurationWriter.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then(_ => this.currentIconTheme);
M
Martin Aeschlimann 已提交
683 684
		}
		return TPromise.as(this.currentIconTheme);
M
Martin Aeschlimann 已提交
685 686
	}

687 688 689 690
	private get configurationWriter(): ConfigurationWriter {
		// separate out the ConfigurationWriter to avoid a dependency of the IConfigurationEditingService
		if (!this._configurationWriter) {
			this._configurationWriter = this.instantiationService.createInstance(ConfigurationWriter);
691
		}
692
		return this._configurationWriter;
693 694
	}

M
Martin Aeschlimann 已提交
695
	private _findIconThemeData(iconTheme: string): TPromise<IInternalIconThemeData> {
696
		return this.getFileIconThemes().then(allIconSets => {
697
			for (let iconSet of allIconSets) {
M
Martin Aeschlimann 已提交
698
				if (iconSet.id === iconTheme) {
M
Martin Aeschlimann 已提交
699
					return <IInternalIconThemeData>iconSet;
700 701
				}
			}
M
Martin Aeschlimann 已提交
702 703 704 705 706 707 708 709 710 711 712 713
			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 已提交
714 715
		});
	}
E
Erich Gamma 已提交
716 717
}

M
Martin Aeschlimann 已提交
718
function _applyIconTheme(data: IInternalIconThemeData, onApply: (theme: IInternalIconThemeData) => TPromise<IFileIconTheme>): TPromise<IFileIconTheme> {
719
	if (!data) {
720
		_applyRules('', iconThemeRulesClassName);
M
Martin Aeschlimann 已提交
721
		return TPromise.as(onApply(data));
722 723
	}

M
Martin Aeschlimann 已提交
724
	if (data.styleSheetContent) {
725
		_applyRules(data.styleSheetContent, iconThemeRulesClassName);
M
Martin Aeschlimann 已提交
726
		return TPromise.as(onApply(data));
M
Martin Aeschlimann 已提交
727
	}
728
	return _loadIconThemeDocument(data.path).then(iconThemeDocument => {
M
Martin Aeschlimann 已提交
729 730 731 732
		let result = _processIconThemeDocument(data.id, data.path, iconThemeDocument);
		data.styleSheetContent = result.content;
		data.hasFileIcons = result.hasFileIcons;
		data.hasFolderIcons = result.hasFolderIcons;
733
		data.isLoaded = true;
M
Martin Aeschlimann 已提交
734 735
		_applyRules(data.styleSheetContent, iconThemeRulesClassName);
		return onApply(data);
M
Martin Aeschlimann 已提交
736
	}, error => {
737
		return TPromise.wrapError<IFileIconTheme>(nls.localize('error.cannotloadicontheme', "Unable to load {0}", data.path));
M
Martin Aeschlimann 已提交
738 739 740
	});
}

J
Johannes Rieken 已提交
741
function _loadIconThemeDocument(fileSetPath: string): TPromise<IconThemeDocument> {
M
Martin Aeschlimann 已提交
742 743
	return pfs.readFile(fileSetPath).then(content => {
		let errors: Json.ParseError[] = [];
M
Martin Aeschlimann 已提交
744
		let contentValue = Json.parse(content.toString(), errors);
M
Martin Aeschlimann 已提交
745
		if (errors.length > 0) {
M
Martin Aeschlimann 已提交
746
			return TPromise.wrapError(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing file icons file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', '))));
M
Martin Aeschlimann 已提交
747 748 749 750 751
		}
		return TPromise.as(contentValue);
	});
}

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

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

756
	if (!iconThemeDocument.iconDefinitions) {
M
Martin Aeschlimann 已提交
757
		return result;
M
Martin Aeschlimann 已提交
758
	}
J
Johannes Rieken 已提交
759
	let selectorByDefinitionId: { [def: string]: string[] } = {};
760

761
	function resolvePath(path: string) {
J
Johannes Rieken 已提交
762
		const uri = URI.file(Paths.join(Paths.dirname(iconThemeDocumentPath), path));
763
		return uri.toString();
764 765
	}

766
	function collectSelectors(associations: IconsAssociation, baseThemeClassName?: string) {
767 768 769 770 771 772 773
		function addSelector(selector: string, defId: string) {
			if (defId) {
				let list = selectorByDefinitionId[defId];
				if (!list) {
					list = selectorByDefinitionId[defId] = [];
				}
				list.push(selector);
M
Martin Aeschlimann 已提交
774 775
			}
		}
776 777 778 779 780 781
		if (associations) {
			let qualifier = '.show-file-icons';
			if (baseThemeClassName) {
				qualifier = baseThemeClassName + ' ' + qualifier;
			}

782 783
			let expanded = '.monaco-tree-row.expanded'; // workaround for #11453

M
Martin Aeschlimann 已提交
784 785 786 787 788 789 790 791 792
			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;
			}
793

M
Martin Aeschlimann 已提交
794 795 796
			if (associations.file) {
				addSelector(`${qualifier} .file-icon::before`, associations.file);
				result.hasFileIcons = true;
797 798
			}

799 800 801 802
			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 已提交
803
					result.hasFolderIcons = true;
804 805
				}
			}
806 807 808
			let folderNamesExpanded = associations.folderNamesExpanded;
			if (folderNamesExpanded) {
				for (let folderName in folderNamesExpanded) {
809
					addSelector(`${qualifier} ${expanded} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNamesExpanded[folderName]);
M
Martin Aeschlimann 已提交
810
					result.hasFolderIcons = true;
811 812
				}
			}
813

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

M
Martin Aeschlimann 已提交
853 854
	if (!result.hasFileIcons && !result.hasFolderIcons) {
		return result;
855 856
	}

M
Martin Aeschlimann 已提交
857
	let cssRules: string[] = [];
858

859
	let fonts = iconThemeDocument.fonts;
860 861 862 863 864 865
	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%'}}`);
866 867
	}

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

897 898 899 900
function escapeCSS(str: string) {
	return window['CSS'].escape(str);
}

M
Martin Aeschlimann 已提交
901
let colorThemeRulesClassName = 'contributedColorTheme';
902
let iconThemeRulesClassName = 'contributedIconTheme';
E
Erich Gamma 已提交
903

M
Martin Aeschlimann 已提交
904 905
function _applyRules(styleSheetContent: string, rulesClassName: string) {
	let themeStyles = document.head.getElementsByClassName(rulesClassName);
E
Erich Gamma 已提交
906
	if (themeStyles.length === 0) {
B
Benjamin Pasero 已提交
907 908
		let elStyle = document.createElement('style');
		elStyle.type = 'text/css';
M
Martin Aeschlimann 已提交
909
		elStyle.className = rulesClassName;
E
Erich Gamma 已提交
910 911 912
		elStyle.innerHTML = styleSheetContent;
		document.head.appendChild(elStyle);
	} else {
B
Benjamin Pasero 已提交
913
		(<HTMLStyleElement>themeStyles[0]).innerHTML = styleSheetContent;
E
Erich Gamma 已提交
914 915 916
	}
}

917 918
colorThemeSchema.register();
fileIconThemeSchema.register();
M
Martin Aeschlimann 已提交
919

920 921 922 923
class ConfigurationWriter {
	constructor( @IConfigurationService private configurationService: IConfigurationService, @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService) {
	}

B
Benjamin Pasero 已提交
924
	public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget): TPromise<void> {
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
		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 });
	}
}

944
// Configuration: Themes
M
Martin Aeschlimann 已提交
945
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
946

947
const colorThemeSettingSchema: IJSONSchema = {
948 949
	type: 'string',
	description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."),
950
	default: DEFAULT_THEME_SETTING_VALUE,
951 952
	enum: [],
	enumDescriptions: [],
953 954
	errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."),
};
955
const iconThemeSettingSchema: IJSONSchema = {
956
	type: ['string', 'null'],
957
	default: DEFAULT_ICON_THEME_SETTING_VALUE,
958 959 960 961 962
	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.")
};
963
const colorCustomizationsSchema: IJSONSchema = {
964 965
	type: ['object'],
	description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."),
966
	properties: colorThemeSchema.colorsSchema.properties,
967
	default: {},
968 969 970 971 972 973 974
	defaultSnippets: [{
		body: {
			'statusBarBackground': '#666666',
			'panelBackground': '#555555',
			'sideBarBackground': '#444444'
		}
	}]
975 976
};

977 978 979 980 981
const deprecatedColorCustomizationsSchema: IJSONSchema = objects.mixin({
	deprecationMessage: nls.localize('workbenchColors.deprecated', "The setting is no longer experimental and has been renamed to 'workbench.colorCustomizations'"),
	description: nls.localize('workbenchColors.deprecatedDescription', "Use 'workbench.colorCustomizations' instead")
}, colorCustomizationsSchema, false);

M
Martin Aeschlimann 已提交
982
configurationRegistry.registerConfiguration({
983 984 985 986
	id: 'workbench',
	order: 7.1,
	type: 'object',
	properties: {
987 988
		[COLOR_THEME_SETTING]: colorThemeSettingSchema,
		[ICON_THEME_SETTING]: iconThemeSettingSchema,
989 990
		[CUSTOM_COLORS_SETTING]: colorCustomizationsSchema,
		[DEPRECATED_CUSTOM_COLORS_SETTING]: deprecatedColorCustomizationsSchema
M
Martin Aeschlimann 已提交
991
	}
992 993
});