workbenchThemeService.ts 28.8 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import * as nls from 'vs/nls';
M
Martin Aeschlimann 已提交
7
import * as types from 'vs/base/common/types';
8
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
9
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, DETECT_HC_SETTING, HC_THEME_ID } from 'vs/workbench/services/themes/common/workbenchThemeService';
B
Benjamin Pasero 已提交
10
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
J
Johannes Rieken 已提交
11
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
12
import { Registry } from 'vs/platform/registry/common/platform';
13
import * as errors from 'vs/base/common/errors';
14
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
15
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
16
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
17
import { ColorThemeData } from './colorThemeData';
18
import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService';
M
Matt Bierner 已提交
19
import { Event, Emitter } from 'vs/base/common/event';
20
import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema';
21
import { IDisposable } from 'vs/base/common/lifecycle';
22
import { ColorThemeStore } from 'vs/workbench/services/themes/browser/colorThemeStore';
23 24
import { FileIconThemeStore } from 'vs/workbench/services/themes/common/fileIconThemeStore';
import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData';
B
Benjamin Pasero 已提交
25
import { IWindowService } from 'vs/platform/windows/common/windows';
26
import { removeClasses, addClasses } from 'vs/base/browser/dom';
27
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
28
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
M
Martin Aeschlimann 已提交
29 30
import { URI } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources';
31 32 33
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema';
import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry';
34

E
Erich Gamma 已提交
35 36
// implementation

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

40
const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData';
41
const PERSISTED_ICON_THEME_STORAGE_KEY = 'iconThemeData';
42

43 44 45
const defaultThemeExtensionId = 'vscode-theme-defaults';
const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults';

46
const DEFAULT_ICON_THEME_SETTING_VALUE = 'vs-seti';
47 48
const fileIconsEnabledClass = 'file-icons-enabled';

49 50 51
const colorThemeRulesClassName = 'contributedColorTheme';
const iconThemeRulesClassName = 'contributedIconTheme';

52 53
const themingRegistry = Registry.as<IThemingRegistry>(ThemingExtensions.ThemingContribution);

J
Johannes Rieken 已提交
54
function validateThemeId(theme: string): string {
55 56
	// migrations
	switch (theme) {
57 58 59
		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`;
60 61 62 63 64 65
		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;
}

66
export interface IColorCustomizations {
67
	[colorIdOrThemeSettingsId: string]: string | IColorCustomizations;
68 69
}

70
export class WorkbenchThemeService implements IWorkbenchThemeService {
71
	_serviceBrand: any;
E
Erich Gamma 已提交
72

A
Alex Dima 已提交
73 74
	private fileService: IFileService;

75
	private colorThemeStore: ColorThemeStore;
76
	private currentColorTheme: ColorThemeData;
M
Martin Aeschlimann 已提交
77
	private container: HTMLElement;
M
Matt Bierner 已提交
78
	private readonly onColorThemeChange: Emitter<IColorTheme>;
79
	private watchedColorThemeLocation: URI | undefined;
M
Martin Aeschlimann 已提交
80

81
	private iconThemeStore: FileIconThemeStore;
M
Martin Aeschlimann 已提交
82
	private currentIconTheme: FileIconThemeData;
M
Matt Bierner 已提交
83
	private readonly onFileIconThemeChange: Emitter<IFileIconTheme>;
84
	private watchedIconThemeLocation: URI | undefined;
E
Erich Gamma 已提交
85

86
	private themingParticipantChangeListener: IDisposable;
87
	private _configurationWriter: ConfigurationWriter;
88

89
	private get colorCustomizations(): IColorCustomizations {
90
		return this.configurationService.getValue<IColorCustomizations>(CUSTOM_WORKBENCH_COLORS_SETTING) || {};
91 92 93
	}

	private get tokenColorCustomizations(): ITokenColorCustomizations {
94
		return this.configurationService.getValue<ITokenColorCustomizations>(CUSTOM_EDITOR_COLORS_SETTING) || {};
95 96
	}

M
Martin Aeschlimann 已提交
97
	constructor(
B
Benjamin Pasero 已提交
98
		container: HTMLElement,
99
		@IExtensionService extensionService: IExtensionService,
100 101 102 103 104 105
		@IStorageService private readonly storageService: IStorageService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@ITelemetryService private readonly telemetryService: ITelemetryService,
		@IWindowService private readonly windowService: IWindowService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService
106
	) {
107

B
Benjamin Pasero 已提交
108
		this.container = container;
109
		this.colorThemeStore = new ColorThemeStore(extensionService, ColorThemeData.createLoadedEmptyTheme(DEFAULT_THEME_ID, DEFAULT_THEME_SETTING_VALUE));
110
		this.onFileIconThemeChange = new Emitter<IFileIconTheme>();
111
		this.iconThemeStore = new FileIconThemeStore(extensionService);
112
		this.onColorThemeChange = new Emitter<IColorTheme>();
113

M
Martin Aeschlimann 已提交
114
		this.currentIconTheme = FileIconThemeData.createUnloadedTheme('');
115

116 117 118
		// 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
119
		let themeData: ColorThemeData | undefined = undefined;
120
		let persistedThemeData = this.storageService.get(PERSISTED_THEME_STORAGE_KEY, StorageScope.GLOBAL);
121
		if (persistedThemeData) {
122
			themeData = ColorThemeData.fromStorageData(persistedThemeData);
123
		}
124
		let containerBaseTheme = this.getBaseThemeFromContainer();
M
Martin Aeschlimann 已提交
125
		if (!themeData || themeData.baseTheme !== containerBaseTheme) {
126
			themeData = ColorThemeData.createUnloadedTheme(containerBaseTheme);
127
		}
128
		themeData.setCustomColors(this.colorCustomizations);
129
		themeData.setCustomTokenColors(this.tokenColorCustomizations);
130
		this.updateDynamicCSSRules(themeData);
131
		this.applyTheme(themeData, undefined, true);
E
Erich Gamma 已提交
132

133
		let persistedIconThemeData = this.storageService.get(PERSISTED_ICON_THEME_STORAGE_KEY, StorageScope.GLOBAL);
134
		if (persistedIconThemeData) {
135
			const iconData = FileIconThemeData.fromStorageData(persistedIconThemeData);
136 137 138
			if (iconData) {
				_applyIconTheme(iconData, () => {
					this.doSetFileIconTheme(iconData);
M
Martin Aeschlimann 已提交
139
					return Promise.resolve(iconData);
140 141 142 143
				});
			}
		}

R
Rob Lourens 已提交
144
		this.initialize().then(undefined, errors.onUnexpectedError).then(_ => {
145
			this.installConfigurationListener();
M
Martin Aeschlimann 已提交
146
		});
147

148 149
		let prevColorId: string | undefined = undefined;

150
		// update settings schema setting for theme specific settings
151
		this.colorThemeStore.onDidChange(async event => {
152 153 154 155
			// updates enum for the 'workbench.colorTheme` setting
			colorThemeSettingSchema.enum = event.themes.map(t => t.settingsId);
			colorThemeSettingSchema.enumDescriptions = event.themes.map(t => t.description || '');

156 157
			const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} };
			const themeSpecificTokenColors: IJSONSchema = { properties: {} };
A
Alex 已提交
158

159 160
			const workbenchColors = { $ref: workbenchColorsSchemaId, additionalProperties: false };
			const tokenColors = { properties: tokenColorSchema.properties, additionalProperties: false };
161
			for (let t of event.themes) {
162
				// add theme specific color customization ("[Abyss]":{ ... })
163
				const themeId = `[${t.settingsId}]`;
164 165
				themeSpecificWorkbenchColors.properties![themeId] = workbenchColors;
				themeSpecificTokenColors.properties![themeId] = tokenColors;
166
			}
A
Alex 已提交
167

168 169
			colorCustomizationsSchema.allOf![1] = themeSpecificWorkbenchColors;
			tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors;
170

171
			configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration);
172

173
			if (this.currentColorTheme.isLoaded) {
174 175 176
				const themeData = await this.colorThemeStore.findThemeData(this.currentColorTheme.id);
				if (!themeData) {
					// current theme is no longer available
177
					prevColorId = this.currentColorTheme.id;
178
					this.setColorTheme(DEFAULT_THEME_ID, 'auto');
179
				} else {
180 181 182 183
					if (this.currentColorTheme.id === DEFAULT_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeStore.findThemeData(prevColorId)) {
						// restore color
						this.setColorTheme(prevColorId, 'auto');
						prevColorId = undefined;
184
					}
185
				}
186
			}
187
		});
188 189 190 191 192

		let prevFileIconId: string | undefined = undefined;
		this.iconThemeStore.onDidChange(async event => {
			iconThemeSettingSchema.enum = [null, ...event.themes.map(t => t.settingsId)];
			iconThemeSettingSchema.enumDescriptions = [iconThemeSettingSchema.enumDescriptions![0], ...event.themes.map(t => t.description || '')];
193
			configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration);
194 195

			if (this.currentIconTheme.isLoaded) {
196 197 198
				const theme = await this.iconThemeStore.findThemeData(this.currentIconTheme.id);
				if (!theme) {
					// current theme is no longer available
199
					prevFileIconId = this.currentIconTheme.id;
200
					this.setFileIconTheme(DEFAULT_ICON_THEME_SETTING_VALUE, 'auto');
201
				} else {
202 203 204 205
					// restore color
					if (this.currentIconTheme.id === DEFAULT_ICON_THEME_SETTING_VALUE && !types.isUndefined(prevFileIconId) && await this.iconThemeStore.findThemeData(prevFileIconId)) {
						this.setFileIconTheme(prevFileIconId, 'auto');
						prevFileIconId = undefined;
206
					}
207
				}
208
			}
209
		});
M
Martin Aeschlimann 已提交
210 211
	}

A
Alex Dima 已提交
212 213
	acquireFileService(fileService: IFileService): void {
		this.fileService = fileService;
M
Martin Aeschlimann 已提交
214 215 216 217 218 219 220

		this.fileService.onFileChanges(async e => {
			if (this.watchedColorThemeLocation && this.currentColorTheme && e.contains(this.watchedColorThemeLocation, FileChangeType.UPDATED)) {
				await this.currentColorTheme.reload(this.fileService);
				this.currentColorTheme.setCustomColors(this.colorCustomizations);
				this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations);
				this.updateDynamicCSSRules(this.currentColorTheme);
221
				this.applyTheme(this.currentColorTheme, undefined, false);
M
Martin Aeschlimann 已提交
222 223 224 225 226 227
			}
			if (this.watchedIconThemeLocation && this.currentIconTheme && e.contains(this.watchedIconThemeLocation, FileChangeType.UPDATED)) {
				await this.currentIconTheme.reload(this.fileService);
				_applyIconTheme(this.currentIconTheme, () => Promise.resolve(this.currentIconTheme));
			}
		});
A
Alex Dima 已提交
228 229
	}

M
Martin Aeschlimann 已提交
230
	public get onDidColorThemeChange(): Event<IColorTheme> {
M
Martin Aeschlimann 已提交
231
		return this.onColorThemeChange.event;
E
Erich Gamma 已提交
232 233
	}

M
Martin Aeschlimann 已提交
234 235 236 237
	public get onDidFileIconThemeChange(): Event<IFileIconTheme> {
		return this.onFileIconThemeChange.event;
	}

238 239 240 241
	public get onIconThemeChange(): Event<IFileIconTheme> {
		return this.onFileIconThemeChange.event;
	}

242 243
	public get onThemeChange(): Event<ITheme> {
		return this.onColorThemeChange.event;
244 245
	}

246
	private initialize(): Promise<[IColorTheme | null, IFileIconTheme | null]> {
B
Benjamin Pasero 已提交
247 248 249 250 251 252 253 254
		let detectHCThemeSetting = this.configurationService.getValue<boolean>(DETECT_HC_SETTING);

		let colorThemeSetting: string;
		if (this.windowService.getConfiguration().highContrast && detectHCThemeSetting) {
			colorThemeSetting = HC_THEME_ID;
		} else {
			colorThemeSetting = this.configurationService.getValue<string>(COLOR_THEME_SETTING);
		}
255

256
		let iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
M
Martin Aeschlimann 已提交
257

M
Martin Aeschlimann 已提交
258
		return Promise.all([
259
			this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => {
260 261 262 263 264 265 266
				return this.colorThemeStore.findThemeDataByParentLocation(this.environmentService.extensionDevelopmentLocationURI).then(devThemes => {
					if (devThemes.length) {
						return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
					} else {
						return this.setColorTheme(theme && theme.id, undefined);
					}
				});
M
Martin Aeschlimann 已提交
267
			}),
268
			this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => {
269 270 271 272 273 274 275
				return this.iconThemeStore.findThemeDataByParentLocation(this.environmentService.extensionDevelopmentLocationURI).then(devThemes => {
					if (devThemes.length) {
						return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
					} else {
						return this.setFileIconTheme(theme && theme.id, undefined);
					}
				});
M
Martin Aeschlimann 已提交
276
			}),
277
		]);
M
Martin Aeschlimann 已提交
278 279
	}

280
	private installConfigurationListener() {
281
		this.configurationService.onDidChangeConfiguration(e => {
282 283 284
			if (e.affectsConfiguration(COLOR_THEME_SETTING)) {
				let colorThemeSetting = this.configurationService.getValue<string>(COLOR_THEME_SETTING);
				if (colorThemeSetting !== this.currentColorTheme.settingsId) {
285
					this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => {
286
						if (theme) {
287
							this.setColorTheme(theme.id, undefined);
288 289 290
						}
					});
				}
291
			}
292
			if (e.affectsConfiguration(ICON_THEME_SETTING)) {
293
				let iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
294 295
				if (iconThemeSetting !== this.currentIconTheme.settingsId) {
					this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => {
296
						this.setFileIconTheme(theme && theme.id, undefined);
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
					});
				}
			}
			if (this.currentColorTheme) {
				let hasColorChanges = false;
				if (e.affectsConfiguration(CUSTOM_WORKBENCH_COLORS_SETTING)) {
					this.currentColorTheme.setCustomColors(this.colorCustomizations);
					hasColorChanges = true;
				}
				if (e.affectsConfiguration(CUSTOM_EDITOR_COLORS_SETTING)) {
					this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations);
					hasColorChanges = true;
				}
				if (hasColorChanges) {
					this.updateDynamicCSSRules(this.currentColorTheme);
					this.onColorThemeChange.fire(this.currentColorTheme);
				}
314 315 316 317
			}
		});
	}

318 319 320 321
	public getColorTheme(): IColorTheme {
		return this.currentColorTheme;
	}

322 323
	public getColorThemes(): Promise<IColorTheme[]> {
		return this.colorThemeStore.getColorThemes();
324 325
	}

326 327 328 329
	public getTheme(): ITheme {
		return this.getColorTheme();
	}

330
	public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise<IColorTheme | null> {
M
Martin Aeschlimann 已提交
331
		if (!themeId) {
M
Martin Aeschlimann 已提交
332
			return Promise.resolve(null);
M
Martin Aeschlimann 已提交
333
		}
M
Martin Aeschlimann 已提交
334
		if (themeId === this.currentColorTheme.id && this.currentColorTheme.isLoaded) {
335
			return this.writeColorThemeConfiguration(settingsTarget);
M
Martin Aeschlimann 已提交
336 337 338 339
		}

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

340 341
		return this.colorThemeStore.findThemeData(themeId, DEFAULT_THEME_ID).then(data => {
			if (!data) {
342 343
				return null;
			}
344
			const themeData = data;
345 346 347 348
			return themeData.ensureLoaded(this.fileService).then(_ => {
				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;
349
					themeData.setCustomColors(this.colorCustomizations);
C
Cody Hoover 已提交
350
					themeData.setCustomTokenColors(this.tokenColorCustomizations);
351 352 353 354 355 356 357
					return Promise.resolve(themeData);
				}
				themeData.setCustomColors(this.colorCustomizations);
				themeData.setCustomTokenColors(this.tokenColorCustomizations);
				this.updateDynamicCSSRules(themeData);
				return this.applyTheme(themeData, settingsTarget);
			}, error => {
358
				return Promise.reject(new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location!.toString(), error.message)));
359
			});
M
Martin Aeschlimann 已提交
360 361 362
		});
	}

363 364 365
	public restoreColorTheme() {
		let colorThemeSetting = this.configurationService.getValue<string>(COLOR_THEME_SETTING);
		if (colorThemeSetting !== this.currentColorTheme.settingsId) {
366
			this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => {
367
				if (theme) {
368
					this.setColorTheme(theme.id, undefined);
369 370 371 372 373
				}
			});
		}
	}

374
	private updateDynamicCSSRules(themeData: ITheme) {
375 376
		let cssRules: string[] = [];
		let hasRule: { [rule: string]: boolean } = {};
377 378 379 380 381 382 383 384
		let ruleCollector = {
			addRule: (rule: string) => {
				if (!hasRule[rule]) {
					cssRules.push(rule);
					hasRule[rule] = true;
				}
			}
		};
385
		themingRegistry.getThemingParticipants().forEach(p => p(themeData, ruleCollector, this.environmentService));
386 387 388
		_applyRules(cssRules.join('\n'), colorThemeRulesClassName);
	}

389
	private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget | undefined | 'auto', silent = false): Promise<IColorTheme | null> {
390 391
		if (this.container) {
			if (this.currentColorTheme) {
392
				removeClasses(this.container, this.currentColorTheme.id);
393
			} else {
394
				removeClasses(this.container, VS_DARK_THEME, VS_LIGHT_THEME, VS_HC_THEME);
395
			}
396
			addClasses(this.container, newTheme.id);
397 398
		}
		this.currentColorTheme = newTheme;
399
		if (!this.themingParticipantChangeListener) {
M
Martin Aeschlimann 已提交
400 401 402 403 404 405
			this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => this.updateDynamicCSSRules(this.currentColorTheme));
		}

		if (this.fileService && !resources.isEqual(newTheme.location, this.watchedColorThemeLocation)) {
			if (this.watchedColorThemeLocation) {
				this.fileService.unwatchFileChanges(this.watchedColorThemeLocation);
406
				this.watchedColorThemeLocation = undefined;
M
Martin Aeschlimann 已提交
407
			}
408 409
			if (newTheme.location && (newTheme.watch || !!this.environmentService.extensionDevelopmentLocationURI)) {
				this.watchedColorThemeLocation = newTheme.location;
M
Martin Aeschlimann 已提交
410 411
				this.fileService.watchFileChanges(this.watchedColorThemeLocation);
			}
412
		}
413

414 415
		this.sendTelemetry(newTheme.id, newTheme.extensionData, 'color');

416
		if (silent) {
M
Martin Aeschlimann 已提交
417
			return Promise.resolve(null);
418 419 420 421 422
		}

		this.onColorThemeChange.fire(this.currentColorTheme);

		// remember theme data for a quick restore
B
Benjamin Pasero 已提交
423
		this.storageService.store(PERSISTED_THEME_STORAGE_KEY, newTheme.toStorageData(), StorageScope.GLOBAL);
424 425

		return this.writeColorThemeConfiguration(settingsTarget);
426
	}
427

428
	private writeColorThemeConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise<IColorTheme> {
429
		if (!types.isUndefinedOrNull(settingsTarget)) {
430
			return this.configurationWriter.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then(_ => this.currentColorTheme);
M
Martin Aeschlimann 已提交
431
		}
M
Martin Aeschlimann 已提交
432
		return Promise.resolve(this.currentColorTheme);
M
Martin Aeschlimann 已提交
433 434
	}

K
katainaka0503 已提交
435
	private themeExtensionsActivated = new Map<string, boolean>();
436
	private sendTelemetry(themeId: string, themeData: ExtensionData | undefined, themeType: string) {
437 438 439
		if (themeData) {
			let key = themeType + themeData.extensionId;
			if (!this.themeExtensionsActivated.get(key)) {
K
kieferrm 已提交
440
				/* __GDPR__
K
kieferrm 已提交
441 442 443
					"activatePlugin" : {
						"id" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
						"name": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
K
kieferrm 已提交
444
						"isBuiltin": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
445
						"publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
K
kieferrm 已提交
446 447 448
						"themeId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
					}
				*/
449 450 451 452
				this.telemetryService.publicLog('activatePlugin', {
					id: themeData.extensionId,
					name: themeData.extensionName,
					isBuiltin: themeData.extensionIsBuiltin,
453
					publisherDisplayName: themeData.extensionPublisher,
454 455 456 457
					themeId: themeId
				});
				this.themeExtensionsActivated.set(key, true);
			}
458 459
		}
	}
M
Martin Aeschlimann 已提交
460

461 462
	public getFileIconThemes(): Promise<IFileIconTheme[]> {
		return this.iconThemeStore.getFileIconThemes();
M
Martin Aeschlimann 已提交
463 464
	}

465
	public getFileIconTheme() {
M
Martin Aeschlimann 已提交
466
		return this.currentIconTheme;
467 468
	}

469 470 471 472
	public getIconTheme() {
		return this.currentIconTheme;
	}

473
	public setFileIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise<IFileIconTheme> {
474
		iconTheme = iconTheme || '';
M
Martin Aeschlimann 已提交
475
		if (iconTheme === this.currentIconTheme.id && this.currentIconTheme.isLoaded) {
M
Martin Aeschlimann 已提交
476
			return this.writeFileIconConfiguration(settingsTarget);
477
		}
478
		let onApply = (newIconTheme: FileIconThemeData) => {
479 480 481
			this.doSetFileIconTheme(newIconTheme);

			// remember theme data for a quick restore
B
Benjamin Pasero 已提交
482
			this.storageService.store(PERSISTED_ICON_THEME_STORAGE_KEY, newIconTheme.toStorageData(), StorageScope.GLOBAL);
M
Martin Aeschlimann 已提交
483 484

			return this.writeFileIconConfiguration(settingsTarget);
485 486
		};

487 488
		return this.iconThemeStore.findThemeData(iconTheme).then(data => {
			const iconThemeData = data || FileIconThemeData.noIconTheme();
A
Alex Dima 已提交
489
			return iconThemeData.ensureLoaded(this.fileService).then(_ => {
490 491
				return _applyIconTheme(iconThemeData, onApply);
			});
M
Martin Aeschlimann 已提交
492 493
		});
	}
M
Martin Aeschlimann 已提交
494

495 496 497 498 499 500 501 502 503 504 505
	public restoreFileIconTheme() {
		let fileIconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
		if (fileIconThemeSetting !== this.currentIconTheme.settingsId) {
			this.iconThemeStore.findThemeBySettingsId(fileIconThemeSetting).then(theme => {
				if (theme) {
					this.setFileIconTheme(theme.id, undefined);
				}
			});
		}
	}

506
	private doSetFileIconTheme(iconThemeData: FileIconThemeData): void {
507
		this.currentIconTheme = iconThemeData;
508 509

		if (this.container) {
510
			if (iconThemeData.id) {
511
				addClasses(this.container, fileIconsEnabledClass);
512
			} else {
513
				removeClasses(this.container, fileIconsEnabledClass);
514 515
			}
		}
M
Martin Aeschlimann 已提交
516 517 518 519

		if (this.fileService && !resources.isEqual(iconThemeData.location, this.watchedIconThemeLocation)) {
			if (this.watchedIconThemeLocation) {
				this.fileService.unwatchFileChanges(this.watchedIconThemeLocation);
520
				this.watchedIconThemeLocation = undefined;
M
Martin Aeschlimann 已提交
521
			}
522 523
			if (iconThemeData.location && (iconThemeData.watch || !!this.environmentService.extensionDevelopmentLocationURI)) {
				this.watchedIconThemeLocation = iconThemeData.location;
M
Martin Aeschlimann 已提交
524 525 526 527
				this.fileService.watchFileChanges(this.watchedIconThemeLocation);
			}
		}

528
		if (iconThemeData.id) {
529 530 531 532 533 534
			this.sendTelemetry(iconThemeData.id, iconThemeData.extensionData, 'fileIcon');
		}
		this.onFileIconThemeChange.fire(this.currentIconTheme);

	}

535
	private writeFileIconConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise<IFileIconTheme> {
536
		if (!types.isUndefinedOrNull(settingsTarget)) {
537
			return this.configurationWriter.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then(_ => this.currentIconTheme);
M
Martin Aeschlimann 已提交
538
		}
M
Martin Aeschlimann 已提交
539
		return Promise.resolve(this.currentIconTheme);
M
Martin Aeschlimann 已提交
540 541
	}

542 543 544 545
	private get configurationWriter(): ConfigurationWriter {
		// separate out the ConfigurationWriter to avoid a dependency of the IConfigurationEditingService
		if (!this._configurationWriter) {
			this._configurationWriter = this.instantiationService.createInstance(ConfigurationWriter);
546
		}
547
		return this._configurationWriter;
548
	}
549 550 551 552 553 554 555 556 557 558 559 560

	private getBaseThemeFromContainer() {
		if (this.container) {
			for (let i = this.container.classList.length - 1; i >= 0; i--) {
				const item = document.body.classList.item(i);
				if (item === VS_LIGHT_THEME || item === VS_DARK_THEME || item === VS_HC_THEME) {
					return item;
				}
			}
		}
		return VS_DARK_THEME;
	}
E
Erich Gamma 已提交
561 562
}

J
Johannes Rieken 已提交
563
function _applyIconTheme(data: FileIconThemeData, onApply: (theme: FileIconThemeData) => Promise<IFileIconTheme>): Promise<IFileIconTheme> {
564
	_applyRules(data.styleSheetContent!, iconThemeRulesClassName);
565
	return onApply(data);
M
Martin Aeschlimann 已提交
566 567 568 569
}

function _applyRules(styleSheetContent: string, rulesClassName: string) {
	let themeStyles = document.head.getElementsByClassName(rulesClassName);
E
Erich Gamma 已提交
570
	if (themeStyles.length === 0) {
B
Benjamin Pasero 已提交
571 572
		let elStyle = document.createElement('style');
		elStyle.type = 'text/css';
M
Martin Aeschlimann 已提交
573
		elStyle.className = rulesClassName;
E
Erich Gamma 已提交
574 575 576
		elStyle.innerHTML = styleSheetContent;
		document.head.appendChild(elStyle);
	} else {
B
Benjamin Pasero 已提交
577
		(<HTMLStyleElement>themeStyles[0]).innerHTML = styleSheetContent;
E
Erich Gamma 已提交
578 579 580
	}
}

581 582
registerColorThemeSchemas();
registerFileIconThemeSchemas();
M
Martin Aeschlimann 已提交
583

584
class ConfigurationWriter {
585
	constructor(@IConfigurationService private readonly configurationService: IConfigurationService) {
586 587
	}

588
	public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget | 'auto'): Promise<void> {
589
		let settings = this.configurationService.inspect(key);
590 591 592 593 594 595 596 597 598 599
		if (settingsTarget === 'auto') {
			if (!types.isUndefined(settings.workspaceFolder)) {
				settingsTarget = ConfigurationTarget.WORKSPACE_FOLDER;
			} else if (!types.isUndefined(settings.workspace)) {
				settingsTarget = ConfigurationTarget.WORKSPACE;
			} else {
				settingsTarget = ConfigurationTarget.USER;
			}
		}

600 601
		if (settingsTarget === ConfigurationTarget.USER) {
			if (value === settings.user) {
R
Rob Lourens 已提交
602
				return Promise.resolve(undefined); // nothing to do
603 604
			} else if (value === settings.default) {
				if (types.isUndefined(settings.user)) {
R
Rob Lourens 已提交
605
					return Promise.resolve(undefined); // nothing to do
606
				}
R
Rob Lourens 已提交
607
				value = undefined; // remove configuration from user settings
608
			}
609
		} else if (settingsTarget === ConfigurationTarget.WORKSPACE || settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER) {
610
			if (value === settings.value) {
R
Rob Lourens 已提交
611
				return Promise.resolve(undefined); // nothing to do
612 613
			}
		}
614
		return this.configurationService.updateValue(key, value, settingsTarget);
615 616 617
	}
}

618
// Configuration: Themes
M
Martin Aeschlimann 已提交
619
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
620

621
const colorThemeSettingSchema: IConfigurationPropertySchema = {
622 623
	type: 'string',
	description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."),
624
	default: DEFAULT_THEME_SETTING_VALUE,
625 626
	enum: [],
	enumDescriptions: [],
627 628
	errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."),
};
629

630
const iconThemeSettingSchema: IConfigurationPropertySchema = {
631
	type: ['string', 'null'],
632
	default: DEFAULT_ICON_THEME_SETTING_VALUE,
633
	description: nls.localize('iconTheme', "Specifies the icon theme used in the workbench or 'null' to not show any file icons."),
634 635 636 637
	enum: [null],
	enumDescriptions: [nls.localize('noIconThemeDesc', 'No file icons')],
	errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.")
};
638
const colorCustomizationsSchema: IConfigurationPropertySchema = {
639
	type: 'object',
640
	description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."),
641
	allOf: [{ $ref: workbenchColorsSchemaId }],
642
	default: {},
643 644 645 646
	defaultSnippets: [{
		body: {
		}
	}]
647 648
};

649
const themeSettingsConfiguration: IConfigurationNode = {
650 651 652 653
	id: 'workbench',
	order: 7.1,
	type: 'object',
	properties: {
654 655
		[COLOR_THEME_SETTING]: colorThemeSettingSchema,
		[ICON_THEME_SETTING]: iconThemeSettingSchema,
656
		[CUSTOM_WORKBENCH_COLORS_SETTING]: colorCustomizationsSchema
M
Martin Aeschlimann 已提交
657
	}
658 659
};
configurationRegistry.registerConfiguration(themeSettingsConfiguration);
660

661 662 663
function tokenGroupSettings(description: string) {
	return {
		description,
664
		default: '#FF0000',
665 666 667
		anyOf: [
			{
				type: 'string',
668
				format: 'color-hex'
669
			},
670 671 672
			{
				$ref: textmateColorSettingsSchemaId
			}
673 674
		]
	};
675
}
676

677 678 679 680 681 682 683 684 685 686 687 688 689 690
const tokenColorSchema: IJSONSchema = {
	properties: {
		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.")),
		textMateRules: {
			description: nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).'),
			$ref: textmateColorsSchemaId
		}
	}
691
};
692
const tokenColorCustomizationSchema: IConfigurationPropertySchema = {
693 694
	description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."),
	default: {},
695
	allOf: [tokenColorSchema]
696
};
697
const tokenColorCustomizationConfiguration: IConfigurationNode = {
698 699 700 701
	id: 'editor',
	order: 7.2,
	type: 'object',
	properties: {
702
		[CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema
703
	}
704
};
705
configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration);
706