workbenchThemeService.ts 24.9 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');
M
Martin Aeschlimann 已提交
9
import * as types from 'vs/base/common/types';
10
import * as objects from 'vs/base/common/objects';
J
Johannes Rieken 已提交
11
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
12
import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, CUSTOM_EDITOR_SCOPE_COLORS_SETTING } from 'vs/workbench/services/themes/common/workbenchThemeService';
J
Johannes Rieken 已提交
13 14
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
15
import { Registry } from 'vs/platform/registry/common/platform';
J
Johannes Rieken 已提交
16
import { IJSONSchema } from 'vs/base/common/jsonSchema';
B
Benjamin Pasero 已提交
17
import errors = require('vs/base/common/errors');
M
Martin Aeschlimann 已提交
18 19 20
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';
21 22
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IMessageService } from 'vs/platform/message/common/message';
23
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
24
import Severity from 'vs/base/common/severity';
25
import { ColorThemeData } from './colorThemeData';
26
import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService';
27
import { editorBackground } from 'vs/platform/theme/common/colorRegistry';
J
Joao Moreno 已提交
28
import { Color } from 'vs/base/common/color';
J
Johannes Rieken 已提交
29 30 31

import { $ } from 'vs/base/browser/builder';
import Event, { Emitter } from 'vs/base/common/event';
E
Erich Gamma 已提交
32 33 34

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

35 36
import colorThemeSchema = require('vs/workbench/services/themes/common/colorThemeSchema');
import fileIconThemeSchema = require('vs/workbench/services/themes/common/fileIconThemeSchema');
37
import { IDisposable } from 'vs/base/common/lifecycle';
38
import { IBroadcastService } from 'vs/platform/broadcast/electron-browser/broadcastService';
39 40 41
import { ColorThemeStore } from 'vs/workbench/services/themes/electron-browser/colorThemeStore';
import { FileIconThemeStore } from 'vs/workbench/services/themes/electron-browser/fileIconThemeStore';
import { FileIconThemeData } from 'vs/workbench/services/themes/electron-browser/fileIconThemeData';
42

E
Erich Gamma 已提交
43 44
// implementation

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

48 49
const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData';

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

53
const DEFAULT_ICON_THEME_SETTING_VALUE = 'vs-seti';
54 55
const fileIconsEnabledClass = 'file-icons-enabled';

56 57 58
const colorThemeRulesClassName = 'contributedColorTheme';
const iconThemeRulesClassName = 'contributedIconTheme';

59 60
const themingRegistry = Registry.as<IThemingRegistry>(ThemingExtensions.ThemingContribution);

J
Johannes Rieken 已提交
61
function validateThemeId(theme: string): string {
62 63
	// migrations
	switch (theme) {
64 65 66
		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`;
67 68 69 70 71 72
		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;
}

73
export interface IColorCustomizations {
74
	[colorId: string]: string;
75 76
}

77
export class WorkbenchThemeService implements IWorkbenchThemeService {
78
	_serviceBrand: any;
E
Erich Gamma 已提交
79

80
	private colorThemeStore: ColorThemeStore;
81
	private colorCustomizations: IColorCustomizations;
82
	private tokenColorCustomizations: ITokenColorCustomizations;
83
	private numberOfColorCustomizations: number;
84
	private currentColorTheme: ColorThemeData;
M
Martin Aeschlimann 已提交
85
	private container: HTMLElement;
M
Martin Aeschlimann 已提交
86
	private onColorThemeChange: Emitter<IColorTheme>;
M
Martin Aeschlimann 已提交
87

88
	private iconThemeStore: FileIconThemeStore;
M
Martin Aeschlimann 已提交
89 90
	private currentIconTheme: IFileIconTheme;
	private onFileIconThemeChange: Emitter<IFileIconTheme>;
E
Erich Gamma 已提交
91

92
	private themingParticipantChangeListener: IDisposable;
93
	private _configurationWriter: ConfigurationWriter;
94

M
Martin Aeschlimann 已提交
95
	constructor(
B
Benjamin Pasero 已提交
96
		container: HTMLElement,
J
Johannes Rieken 已提交
97 98
		@IExtensionService private extensionService: IExtensionService,
		@IStorageService private storageService: IStorageService,
99
		@IBroadcastService private broadcastService: IBroadcastService,
M
Martin Aeschlimann 已提交
100
		@IConfigurationService private configurationService: IConfigurationService,
101 102
		@IEnvironmentService private environmentService: IEnvironmentService,
		@IMessageService private messageService: IMessageService,
103 104
		@ITelemetryService private telemetryService: ITelemetryService,
		@IInstantiationService private instantiationService: IInstantiationService) {
105

B
Benjamin Pasero 已提交
106
		this.container = container;
107
		this.colorThemeStore = new ColorThemeStore(extensionService);
108
		this.colorCustomizations = {};
109
		this.tokenColorCustomizations = {};
110
		this.onFileIconThemeChange = new Emitter<IFileIconTheme>();
111
		this.iconThemeStore = new FileIconThemeStore(extensionService);
112 113
		this.onColorThemeChange = new Emitter<IColorTheme>();

M
Martin Aeschlimann 已提交
114 115 116
		this.currentIconTheme = {
			id: '',
			label: '',
M
Martin Aeschlimann 已提交
117
			settingsId: null,
M
Martin Aeschlimann 已提交
118 119
			isLoaded: false,
			hasFileIcons: false,
M
Martin Aeschlimann 已提交
120 121
			hasFolderIcons: false,
			extensionData: null
M
Martin Aeschlimann 已提交
122
		};
123

124 125
		this.updateColorCustomizations(false);

126 127 128
		// 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
129
		let themeData = null;
130 131
		let persistedThemeData = this.storageService.get(PERSISTED_THEME_STORAGE_KEY);
		if (persistedThemeData) {
132
			themeData = ColorThemeData.fromStorageData(persistedThemeData);
133
		}
134
		if (!themeData) {
135
			let isLightTheme = (Array.prototype.indexOf.call(document.body.classList, 'vs') >= 0);
136
			themeData = ColorThemeData.createUnloadedTheme(isLightTheme ? VS_LIGHT_THEME : VS_DARK_THEME);
137
		}
138
		themeData.setCustomColors(this.colorCustomizations);
139
		themeData.setCustomTokenColors(this.tokenColorCustomizations);
140 141
		this.updateDynamicCSSRules(themeData);
		this.applyTheme(themeData, null, true);
E
Erich Gamma 已提交
142

143 144 145
		this.migrate().then(_ => {
			this.initialize().then(null, errors.onUnexpectedError).then(_ => {
				this.installConfigurationListener();
146
			});
M
Martin Aeschlimann 已提交
147
		});
148 149 150 151 152 153 154 155 156 157 158 159

		// update settings schema setting
		this.colorThemeStore.onDidChange(themes => {
			colorThemeSettingSchema.enum = themes.map(t => t.settingsId);
			colorThemeSettingSchema.enumDescriptions = themes.map(t => themeData.description || '');
			configurationRegistry.notifyConfigurationSchemaUpdated(colorThemeSettingSchema);
		});
		this.iconThemeStore.onDidChange(themes => {
			iconThemeSettingSchema.enum = themes.map(t => t.settingsId);
			iconThemeSettingSchema.enumDescriptions = themes.map(t => themeData.description || '');
			configurationRegistry.notifyConfigurationSchemaUpdated(iconThemeSettingSchema);
		});
M
Martin Aeschlimann 已提交
160 161
	}

M
Martin Aeschlimann 已提交
162
	public get onDidColorThemeChange(): Event<IColorTheme> {
M
Martin Aeschlimann 已提交
163
		return this.onColorThemeChange.event;
E
Erich Gamma 已提交
164 165
	}

M
Martin Aeschlimann 已提交
166 167 168 169
	public get onDidFileIconThemeChange(): Event<IFileIconTheme> {
		return this.onFileIconThemeChange.event;
	}

170 171
	public get onThemeChange(): Event<ITheme> {
		return this.onColorThemeChange.event;
172 173
	}

174
	private backupSettings(): TPromise<string> {
175 176 177 178 179
		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 => {
180
			if (err && err.code === 'ENOENT') {
181
				return TPromise.as<string>(null); // ignore, user config file doesn't exist yet
182
			};
183
			return TPromise.wrapError<string>(err);
184 185 186
		});
	}

B
Benjamin Pasero 已提交
187
	private migrate(): TPromise<void> {
188 189
		let legacyColorThemeId = this.storageService.get('workbench.theme', StorageScope.GLOBAL, void 0);
		let legacyIconThemeId = this.storageService.get('workbench.iconTheme', StorageScope.GLOBAL, void 0);
190 191
		if (types.isUndefined(legacyColorThemeId) && types.isUndefined(legacyIconThemeId)) {
			return TPromise.as(null);
M
Martin Aeschlimann 已提交
192
		}
193 194 195 196
		return this.backupSettings().then(backupLocation => {
			let promise = TPromise.as(null);
			if (!types.isUndefined(legacyColorThemeId)) {
				this.storageService.remove('workbench.theme', StorageScope.GLOBAL);
197
				promise = this.colorThemeStore.findThemeData(legacyColorThemeId, DEFAULT_THEME_ID).then(theme => {
198
					let value = theme ? theme.settingsId : DEFAULT_THEME_SETTING_VALUE;
199
					return this.configurationWriter.writeConfiguration(COLOR_THEME_SETTING, value, ConfigurationTarget.USER).then(null, error => null);
200 201 202 203 204
				});
			}
			if (!types.isUndefined(legacyIconThemeId)) {
				this.storageService.remove('workbench.iconTheme', StorageScope.GLOBAL);
				promise = promise.then(_ => {
205
					return this.iconThemeStore.findThemeData(legacyIconThemeId).then(theme => {
206
						let value = theme ? theme.settingsId : null;
207
						return this.configurationWriter.writeConfiguration(ICON_THEME_SETTING, value, ConfigurationTarget.USER).then(null, error => null);
208 209 210 211 212 213 214 215 216 217 218
					});
				});
			}
			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);
				}
			});
		});
219
	}
M
Martin Aeschlimann 已提交
220

R
Ron Buckton 已提交
221
	private initialize(): TPromise<[IColorTheme, IFileIconTheme]> {
222

223
		this.updateColorCustomizations(false);
224

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

228
		return Promise.join([
229
			this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => {
M
Martin Aeschlimann 已提交
230 231
				return this.setColorTheme(theme && theme.id, null);
			}),
232
			this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => {
M
Martin Aeschlimann 已提交
233 234
				return this.setFileIconTheme(theme && theme.id, null);
			}),
235
		]);
M
Martin Aeschlimann 已提交
236 237
	}

238 239 240 241
	private installConfigurationListener() {
		this.configurationService.onDidUpdateConfiguration(e => {
			let colorThemeSetting = this.configurationService.lookup<string>(COLOR_THEME_SETTING).value;
			if (colorThemeSetting !== this.currentColorTheme.settingsId) {
242
				this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, null).then(theme => {
243 244 245 246 247 248 249 250
					if (theme) {
						this.setColorTheme(theme.id, null);
					}
				});
			}

			let iconThemeSetting = this.configurationService.lookup<string>(ICON_THEME_SETTING).value || '';
			if (iconThemeSetting !== this.currentIconTheme.settingsId) {
251
				this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => {
252 253 254
					this.setFileIconTheme(theme && theme.id, null);
				});
			}
255

256
			this.updateColorCustomizations();
257 258 259
		});
	}

260 261 262 263 264 265 266 267
	public getColorTheme(): IColorTheme {
		return this.currentColorTheme;
	}

	public getColorThemes(): TPromise<IColorTheme[]> {
		return this.colorThemeStore.getColorThemes();
	}

268 269 270 271
	public getTheme(): ITheme {
		return this.getColorTheme();
	}

M
Martin Aeschlimann 已提交
272
	public setColorTheme(themeId: string, settingsTarget: ConfigurationTarget): TPromise<IColorTheme> {
M
Martin Aeschlimann 已提交
273
		if (!themeId) {
M
Martin Aeschlimann 已提交
274
			return TPromise.as(null);
M
Martin Aeschlimann 已提交
275
		}
M
Martin Aeschlimann 已提交
276
		if (themeId === this.currentColorTheme.id && this.currentColorTheme.isLoaded) {
277
			return this.writeColorThemeConfiguration(settingsTarget);
M
Martin Aeschlimann 已提交
278 279 280 281
		}

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

282
		return this.colorThemeStore.findThemeData(themeId, DEFAULT_THEME_ID).then(themeData => {
M
Martin Aeschlimann 已提交
283
			if (themeData) {
284
				return themeData.ensureLoaded(this).then(_ => {
285
					if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) {
286
						// the loaded theme is identical to the perisisted theme. Don't need to send an event.
287
						this.currentColorTheme = themeData;
288
						themeData.setCustomColors(this.colorCustomizations);
C
Cody Hoover 已提交
289
						themeData.setCustomTokenColors(this.tokenColorCustomizations);
290 291
						return TPromise.as(themeData);
					}
292
					themeData.setCustomColors(this.colorCustomizations);
C
Cody Hoover 已提交
293
					themeData.setCustomTokenColors(this.tokenColorCustomizations);
294
					this.updateDynamicCSSRules(themeData);
295
					return this.applyTheme(themeData, settingsTarget);
296
				}, error => {
297
					return TPromise.wrapError<IColorTheme>(new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.path, error.message)));
298
				});
M
Martin Aeschlimann 已提交
299 300 301 302 303
			}
			return null;
		});
	}

304
	private updateDynamicCSSRules(themeData: ITheme) {
305 306
		let cssRules: string[] = [];
		let hasRule: { [rule: string]: boolean } = {};
307 308 309 310 311 312 313 314 315 316 317 318
		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);
	}

319
	private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget, silent = false): TPromise<IColorTheme> {
320 321 322
		if (this.container) {
			if (this.currentColorTheme) {
				$(this.container).removeClass(this.currentColorTheme.id);
323 324
			} else {
				$(this.container).removeClass(VS_DARK_THEME, VS_LIGHT_THEME, VS_HC_THEME);
325 326 327 328
			}
			$(this.container).addClass(newTheme.id);
		}
		this.currentColorTheme = newTheme;
329 330 331
		if (!this.themingParticipantChangeListener) {
			this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(p => this.updateDynamicCSSRules(this.currentColorTheme));
		}
332

333 334
		this.sendTelemetry(newTheme.id, newTheme.extensionData, 'color');

335 336 337 338 339 340 341
		if (silent) {
			return TPromise.as(null);
		}

		this.onColorThemeChange.fire(this.currentColorTheme);

		if (settingsTarget !== ConfigurationTarget.WORKSPACE) {
J
Joao Moreno 已提交
342
			let background = Color.Format.CSS.formatHex(newTheme.getColor(editorBackground)); // only take RGB, its what is used in the initial CSS
343
			let data = { id: newTheme.id, background: background };
344
			this.broadcastService.broadcast({ channel: 'vscode:changeColorTheme', payload: JSON.stringify(data) });
345 346 347 348 349 350 351
		}
		// remember theme data for a quick restore
		this.storageService.store(PERSISTED_THEME_STORAGE_KEY, newTheme.toStorageData());

		return this.writeColorThemeConfiguration(settingsTarget);
	};

352
	private writeColorThemeConfiguration(settingsTarget: ConfigurationTarget): TPromise<IColorTheme> {
353
		if (!types.isUndefinedOrNull(settingsTarget)) {
354
			return this.configurationWriter.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then(_ => this.currentColorTheme);
M
Martin Aeschlimann 已提交
355 356
		}
		return TPromise.as(this.currentColorTheme);
M
Martin Aeschlimann 已提交
357 358
	}

359
	private hasCustomizationChanged(newColorCustomizations: IColorCustomizations, newColorIds: string[], newTokenColorCustomizations: ITokenColorCustomizations): boolean {
360
		if (newColorIds.length !== this.numberOfColorCustomizations) {
361 362
			return true;
		}
363 364 365
		for (let key of newColorIds) {
			let color = this.colorCustomizations[key];
			if (!color || color !== newColorCustomizations[key]) {
366
				return true;
367 368
			}
		}
C
Cody Hoover 已提交
369 370 371 372 373

		if (!objects.equals(newTokenColorCustomizations, this.tokenColorCustomizations)) {
			return true;
		}

374 375 376 377
		return false;
	}

	private updateColorCustomizations(notify = true): void {
C
Cody Hoover 已提交
378
		let newColorCustomizations = this.configurationService.lookup<IColorCustomizations>(CUSTOM_WORKBENCH_COLORS_SETTING).value || {};
379
		let newColorIds = Object.keys(newColorCustomizations);
C
Cody Hoover 已提交
380

381
		let newTokenColorCustomizations = this.configurationService.lookup<ITokenColorCustomizations>(CUSTOM_EDITOR_COLORS_SETTING).value || {};
C
Cody Hoover 已提交
382

383
		if (this.hasCustomizationChanged(newColorCustomizations, newColorIds, newTokenColorCustomizations)) {
384 385
			this.colorCustomizations = newColorCustomizations;
			this.numberOfColorCustomizations = newColorIds.length;
386
			this.tokenColorCustomizations = newTokenColorCustomizations;
C
Cody Hoover 已提交
387

388 389
			if (this.currentColorTheme) {
				this.currentColorTheme.setCustomColors(newColorCustomizations);
390
				this.currentColorTheme.setCustomTokenColors(newTokenColorCustomizations);
391 392 393 394
				if (notify) {
					this.updateDynamicCSSRules(this.currentColorTheme);
					this.onColorThemeChange.fire(this.currentColorTheme);
				}
395
			}
396 397 398
		}
	}

K
katainaka0503 已提交
399
	private themeExtensionsActivated = new Map<string, boolean>();
M
Martin Aeschlimann 已提交
400
	private sendTelemetry(themeId: string, themeData: ExtensionData, themeType: string) {
401 402 403
		if (themeData) {
			let key = themeType + themeData.extensionId;
			if (!this.themeExtensionsActivated.get(key)) {
K
kieferrm 已提交
404
				/* __GDPR__
K
kieferrm 已提交
405 406 407 408 409 410 411 412
					"activatePlugin" : {
						"id" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
						"name": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
						"isBuiltin": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
						"publisherDisplayName": { "classification": "PublicPersonalData", "purpose": "FeatureInsight" },
						"themeId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
					}
				*/
413 414 415 416 417 418 419 420 421
				this.telemetryService.publicLog('activatePlugin', {
					id: themeData.extensionId,
					name: themeData.extensionName,
					isBuiltin: themeData.extensionIsBuiltin,
					publisherDisplayName: themeData.extensionPublisher,
					themeId: themeId
				});
				this.themeExtensionsActivated.set(key, true);
			}
422 423
		}
	}
M
Martin Aeschlimann 已提交
424

M
Martin Aeschlimann 已提交
425
	public getFileIconThemes(): TPromise<IFileIconTheme[]> {
426
		return this.iconThemeStore.getFileIconThemes();
M
Martin Aeschlimann 已提交
427 428
	}

429
	public getFileIconTheme() {
M
Martin Aeschlimann 已提交
430
		return this.currentIconTheme;
431 432
	}

M
Martin Aeschlimann 已提交
433
	public setFileIconTheme(iconTheme: string, settingsTarget: ConfigurationTarget): TPromise<IFileIconTheme> {
434
		iconTheme = iconTheme || '';
M
Martin Aeschlimann 已提交
435
		if (iconTheme === this.currentIconTheme.id && this.currentIconTheme.isLoaded) {
M
Martin Aeschlimann 已提交
436
			return this.writeFileIconConfiguration(settingsTarget);
437
		}
438
		let onApply = (newIconTheme: FileIconThemeData) => {
M
Martin Aeschlimann 已提交
439 440 441
			if (newIconTheme) {
				this.currentIconTheme = newIconTheme;
			} else {
442
				this.currentIconTheme = FileIconThemeData.noIconTheme();
M
Martin Aeschlimann 已提交
443
			}
M
Martin Aeschlimann 已提交
444

445 446 447 448 449 450 451
			if (this.container) {
				if (newIconTheme) {
					$(this.container).addClass(fileIconsEnabledClass);
				} else {
					$(this.container).removeClass(fileIconsEnabledClass);
				}
			}
M
Martin Aeschlimann 已提交
452
			if (newIconTheme) {
M
Martin Aeschlimann 已提交
453
				this.sendTelemetry(newIconTheme.id, newIconTheme.extensionData, 'fileIcon');
454
			}
M
Martin Aeschlimann 已提交
455
			this.onFileIconThemeChange.fire(this.currentIconTheme);
M
Martin Aeschlimann 已提交
456
			return this.writeFileIconConfiguration(settingsTarget);
457 458
		};

459
		return this.iconThemeStore.findThemeData(iconTheme).then(iconThemeData => {
M
Martin Aeschlimann 已提交
460 461 462
			return _applyIconTheme(iconThemeData, onApply);
		});
	}
M
Martin Aeschlimann 已提交
463

M
Martin Aeschlimann 已提交
464
	private writeFileIconConfiguration(settingsTarget: ConfigurationTarget): TPromise<IFileIconTheme> {
465
		if (!types.isUndefinedOrNull(settingsTarget)) {
466
			return this.configurationWriter.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then(_ => this.currentIconTheme);
M
Martin Aeschlimann 已提交
467 468
		}
		return TPromise.as(this.currentIconTheme);
M
Martin Aeschlimann 已提交
469 470
	}

471 472 473 474
	private get configurationWriter(): ConfigurationWriter {
		// separate out the ConfigurationWriter to avoid a dependency of the IConfigurationEditingService
		if (!this._configurationWriter) {
			this._configurationWriter = this.instantiationService.createInstance(ConfigurationWriter);
475
		}
476
		return this._configurationWriter;
477
	}
E
Erich Gamma 已提交
478 479
}

480
function _applyIconTheme(data: FileIconThemeData, onApply: (theme: FileIconThemeData) => TPromise<IFileIconTheme>): TPromise<IFileIconTheme> {
481
	if (!data) {
482
		_applyRules('', iconThemeRulesClassName);
M
Martin Aeschlimann 已提交
483
		return TPromise.as(onApply(data));
484
	}
485 486
	return data.ensureLoaded(this).then(styleSheetContent => {
		_applyRules(styleSheetContent, iconThemeRulesClassName);
M
Martin Aeschlimann 已提交
487
		return onApply(data);
M
Martin Aeschlimann 已提交
488
	}, error => {
489
		return TPromise.wrapError<IFileIconTheme>(new Error(nls.localize('error.cannotloadicontheme', "Unable to load {0}", data.path)));
M
Martin Aeschlimann 已提交
490 491 492 493 494
	});
}

function _applyRules(styleSheetContent: string, rulesClassName: string) {
	let themeStyles = document.head.getElementsByClassName(rulesClassName);
E
Erich Gamma 已提交
495
	if (themeStyles.length === 0) {
B
Benjamin Pasero 已提交
496 497
		let elStyle = document.createElement('style');
		elStyle.type = 'text/css';
M
Martin Aeschlimann 已提交
498
		elStyle.className = rulesClassName;
E
Erich Gamma 已提交
499 500 501
		elStyle.innerHTML = styleSheetContent;
		document.head.appendChild(elStyle);
	} else {
B
Benjamin Pasero 已提交
502
		(<HTMLStyleElement>themeStyles[0]).innerHTML = styleSheetContent;
E
Erich Gamma 已提交
503 504 505
	}
}

506 507
colorThemeSchema.register();
fileIconThemeSchema.register();
M
Martin Aeschlimann 已提交
508

509 510 511 512
class ConfigurationWriter {
	constructor( @IConfigurationService private configurationService: IConfigurationService, @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService) {
	}

B
Benjamin Pasero 已提交
513
	public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget): TPromise<void> {
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
		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 });
	}
}

533
// Configuration: Themes
M
Martin Aeschlimann 已提交
534
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
535

536
const colorThemeSettingSchema: IJSONSchema = {
537 538
	type: 'string',
	description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."),
539
	default: DEFAULT_THEME_SETTING_VALUE,
540 541
	enum: [],
	enumDescriptions: [],
542 543
	errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."),
};
544

545
const iconThemeSettingSchema: IJSONSchema = {
546
	type: ['string', 'null'],
547
	default: DEFAULT_ICON_THEME_SETTING_VALUE,
548
	description: nls.localize('iconTheme', "Specifies the icon theme used in the workbench or 'null' to not show any file icons."),
549 550 551 552
	enum: [null],
	enumDescriptions: [nls.localize('noIconThemeDesc', 'No file icons')],
	errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.")
};
553
const colorCustomizationsSchema: IJSONSchema = {
554 555
	type: ['object'],
	description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."),
556
	properties: colorThemeSchema.colorsSchema.properties,
M
Martin Aeschlimann 已提交
557
	additionalProperties: false,
558
	default: {},
559 560
	defaultSnippets: [{
		body: {
561 562 563
			'statusBar.background': '#666666',
			'panel.background': '#555555',
			'sideBar.background': '#444444'
564 565
		}
	}]
566 567
};

M
Martin Aeschlimann 已提交
568
configurationRegistry.registerConfiguration({
569 570 571 572
	id: 'workbench',
	order: 7.1,
	type: 'object',
	properties: {
573 574
		[COLOR_THEME_SETTING]: colorThemeSettingSchema,
		[ICON_THEME_SETTING]: iconThemeSettingSchema,
575
		[CUSTOM_WORKBENCH_COLORS_SETTING]: colorCustomizationsSchema
M
Martin Aeschlimann 已提交
576
	}
577 578
});

579 580 581
function tokenGroupSettings(description: string) {
	return {
		description,
582
		default: '#FF0000',
583 584 585 586 587 588 589 590 591
		anyOf: [
			{
				type: 'string',
				format: 'color',
				defaultSnippets: [{ body: '#FF0000' }]
			},
			colorThemeSchema.tokenColorizationSettingSchema
		]
	};
592 593 594 595 596 597 598 599 600
};

configurationRegistry.registerConfiguration({
	id: 'editor',
	order: 7.2,
	type: 'object',
	properties: {
		[CUSTOM_EDITOR_COLORS_SETTING]: {
			description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."),
601
			default: {},
M
Martin Aeschlimann 已提交
602
			additionalProperties: false,
603
			properties: {
604 605 606 607 608 609 610 611
				comments: tokenGroupSettings(nls.localize('editorColors.comments', "Sets the colors and styles for comments")),
				strings: tokenGroupSettings(nls.localize('editorColors.strings', "Sets the colors and styles for strings literals.")),
				keywords: tokenGroupSettings(nls.localize('editorColors.keywords', "Sets the colors and styles for keywords.")),
				numbers: tokenGroupSettings(nls.localize('editorColors.numbers', "Sets the colors and styles for number literals.")),
				types: tokenGroupSettings(nls.localize('editorColors.types', "Sets the colors and styles for type declarations and references.")),
				functions: tokenGroupSettings(nls.localize('editorColors.functions', "Sets the colors and styles for functions declarations and references.")),
				variables: tokenGroupSettings(nls.localize('editorColors.variables', "Sets the colors and styles for variables declarations and references.")),
				[CUSTOM_EDITOR_SCOPE_COLORS_SETTING]: colorThemeSchema.tokenColorsSchema(nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).'))
612 613 614 615 616
			}
		}
	}
});