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

J
Johannes Rieken 已提交
6 7 8
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { firstIndex } from 'vs/base/common/arrays';
C
Christof Marti 已提交
9
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
I
isidor 已提交
10
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
11
import { Registry } from 'vs/platform/registry/common/platform';
B
Benjamin Pasero 已提交
12
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
13
import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IColorTheme, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
14
import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions';
J
Johannes Rieken 已提交
15
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
B
Benjamin Pasero 已提交
16
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
17
import { IColorRegistry, Extensions as ColorRegistryExtensions } from 'vs/platform/theme/common/colorRegistry';
18
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
J
Joao Moreno 已提交
19
import { Color } from 'vs/base/common/color';
20
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
21
import { LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
22
import { colorThemeSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema';
23
import { onUnexpectedError } from 'vs/base/common/errors';
24
import { IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
E
Erich Gamma 已提交
25

C
Christof Marti 已提交
26
export class SelectColorThemeAction extends Action {
E
Erich Gamma 已提交
27

28
	static readonly ID = 'workbench.action.selectTheme';
29
	static readonly LABEL = localize('selectTheme.label', "Color Theme");
E
Erich Gamma 已提交
30 31 32 33

	constructor(
		id: string,
		label: string,
34 35 36 37
		@IQuickInputService private readonly quickInputService: IQuickInputService,
		@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
		@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
		@IViewletService private readonly viewletService: IViewletService,
38
		@IConfigurationService private readonly configurationService: IConfigurationService
E
Erich Gamma 已提交
39 40 41 42
	) {
		super(id, label);
	}

J
Johannes Rieken 已提交
43
	run(): Promise<void> {
M
Martin Aeschlimann 已提交
44
		return this.themeService.getColorThemes().then(themes => {
M
Martin Aeschlimann 已提交
45
			const currentTheme = this.themeService.getColorTheme();
E
Erich Gamma 已提交
46

47 48 49 50 51 52
			const picks: QuickPickInput<ThemeItem>[] = [
				...toEntries(themes.filter(t => t.type === LIGHT), localize('themes.category.light', "light themes")),
				...toEntries(themes.filter(t => t.type === DARK), localize('themes.category.dark', "dark themes")),
				...toEntries(themes.filter(t => t.type === HIGH_CONTRAST), localize('themes.category.hc', "high contrast themes")),
				...configurationEntries(this.extensionGalleryService, localize('installColorThemes', "Install Additional Color Themes..."))
			];
E
Erich Gamma 已提交
53

M
Martin Aeschlimann 已提交
54
			let selectThemeTimeout: number | undefined;
55

56
			const selectTheme = (theme: ThemeItem, applyTheme: boolean) => {
57 58 59
				if (selectThemeTimeout) {
					clearTimeout(selectThemeTimeout);
				}
M
Martin Aeschlimann 已提交
60
				selectThemeTimeout = window.setTimeout(() => {
61
					selectThemeTimeout = undefined;
62
					const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id;
63
					let target: ConfigurationTarget | undefined = undefined;
C
Christof Marti 已提交
64
					if (applyTheme) {
65
						const confValue = this.configurationService.inspect(COLOR_THEME_SETTING);
66
						target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER;
C
Christof Marti 已提交
67
					}
M
Martin Aeschlimann 已提交
68

69 70 71 72 73 74 75
					this.themeService.setColorTheme(themeId, target).then(undefined,
						err => {
							onUnexpectedError(err);
							this.themeService.setColorTheme(currentTheme.id, undefined);
						}
					);
				}, applyTheme ? 0 : 200);
J
Joao Moreno 已提交
76
			};
E
Erich Gamma 已提交
77

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
			return new Promise((s, _) => {
				let isCompleted = false;

				const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id);
				const quickpick = this.quickInputService.createQuickPick<ThemeItem>();
				quickpick.items = picks;
				quickpick.placeholder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)");
				quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem];
				quickpick.canSelectMany = false;
				quickpick.onDidAccept(_ => {
					const theme = quickpick.activeItems[0];
					if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry
						openExtensionViewlet(this.viewletService, `category:themes ${quickpick.value}`);
					} else {
						selectTheme(theme, true);
					}
					isCompleted = true;
					quickpick.hide();
					s();
				});
				quickpick.onDidChangeActive(themes => selectTheme(themes[0], false));
				quickpick.onDidHide(() => {
					if (!isCompleted) {
						selectTheme(currentTheme, true);
						s();
					}
				});
				quickpick.show();
			});
E
Erich Gamma 已提交
107 108 109 110
		});
	}
}

111 112
class SelectIconThemeAction extends Action {

113
	static readonly ID = 'workbench.action.selectIconTheme';
114
	static readonly LABEL = localize('selectIconTheme.label', "File Icon Theme");
115 116 117 118

	constructor(
		id: string,
		label: string,
119 120 121 122
		@IQuickInputService private readonly quickInputService: IQuickInputService,
		@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
		@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
		@IViewletService private readonly viewletService: IViewletService,
123
		@IConfigurationService private readonly configurationService: IConfigurationService
M
Martin Aeschlimann 已提交
124

125 126 127 128
	) {
		super(id, label);
	}

J
Johannes Rieken 已提交
129
	run(): Promise<void> {
130
		return this.themeService.getFileIconThemes().then(themes => {
M
Martin Aeschlimann 已提交
131
			const currentTheme = this.themeService.getFileIconTheme();
132

133
			let picks: QuickPickInput<ThemeItem>[] = [{ id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable file icons') }];
134 135
			picks = picks.concat(
				toEntries(themes),
C
Christof Marti 已提交
136
				configurationEntries(this.extensionGalleryService, localize('installIconThemes', "Install Additional File Icon Themes..."))
137
			);
138

M
Martin Aeschlimann 已提交
139
			let selectThemeTimeout: number | undefined;
140

141
			const selectTheme = (theme: ThemeItem, applyTheme: boolean) => {
142 143
				if (selectThemeTimeout) {
					clearTimeout(selectThemeTimeout);
144
				}
M
Martin Aeschlimann 已提交
145
				selectThemeTimeout = window.setTimeout(() => {
146
					selectThemeTimeout = undefined;
147
					const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id;
148 149
					let target: ConfigurationTarget | undefined = undefined;
					if (applyTheme) {
150
						const confValue = this.configurationService.inspect(ICON_THEME_SETTING);
151
						target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER;
152
					}
153 154 155 156 157 158 159
					this.themeService.setFileIconTheme(themeId, target).then(undefined,
						err => {
							onUnexpectedError(err);
							this.themeService.setFileIconTheme(currentTheme.id, undefined);
						}
					);
				}, applyTheme ? 0 : 200);
160 161
			};

162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
			return new Promise((s, _) => {
				let isCompleted = false;

				const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id);
				const quickpick = this.quickInputService.createQuickPick<ThemeItem>();
				quickpick.items = picks;
				quickpick.placeholder = localize('themes.selectIconTheme', "Select File Icon Theme");
				quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem];
				quickpick.canSelectMany = false;
				quickpick.onDidAccept(_ => {
					const theme = quickpick.activeItems[0];
					if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry
						openExtensionViewlet(this.viewletService, `tag:icon-theme ${quickpick.value}`);
					} else {
						selectTheme(theme, true);
					}
					isCompleted = true;
					quickpick.hide();
					s();
				});
				quickpick.onDidChangeActive(themes => selectTheme(themes[0], false));
				quickpick.onDidHide(() => {
					if (!isCompleted) {
						selectTheme(currentTheme, true);
						s();
					}
				});
				quickpick.show();
			});
191 192 193 194
		});
	}
}

195
function configurationEntries(extensionGalleryService: IExtensionGalleryService, label: string): QuickPickInput<ThemeItem>[] {
196
	if (extensionGalleryService.isEnabled()) {
C
Christof Marti 已提交
197 198
		return [
			{
C
Christof Marti 已提交
199
				type: 'separator'
C
Christof Marti 已提交
200 201
			},
			{
R
Rob Lourens 已提交
202
				id: undefined,
C
Christof Marti 已提交
203
				label: label,
204
				alwaysShow: true
C
Christof Marti 已提交
205 206
			}
		];
207 208 209 210
	}
	return [];
}

C
Christof Marti 已提交
211 212
function openExtensionViewlet(viewletService: IViewletService, query: string) {
	return viewletService.openViewlet(VIEWLET_ID, true).then(viewlet => {
213 214 215 216
		if (viewlet) {
			(viewlet as IExtensionsViewlet).search(query);
			viewlet.focus();
		}
C
Christof Marti 已提交
217 218
	});
}
219 220 221 222 223 224 225 226
interface ThemeItem {
	id: string | undefined;
	label: string;
	description?: string;
	alwaysShow?: boolean;
}

function isItem(i: QuickPickInput<ThemeItem>): i is ThemeItem {
227
	return (<any>i)['type'] !== 'separator';
228
}
C
Christof Marti 已提交
229

230 231 232 233
function toEntries(themes: Array<IColorTheme | IFileIconTheme>, label?: string): QuickPickInput<ThemeItem>[] {
	const toEntry = (theme: IColorTheme): ThemeItem => ({ id: theme.id, label: theme.label, description: theme.description });
	const sorter = (t1: ThemeItem, t2: ThemeItem) => t1.label.localeCompare(t2.label);
	let entries: QuickPickInput<ThemeItem>[] = themes.map(toEntry).sort(sorter);
C
Christof Marti 已提交
234 235
	if (entries.length > 0 && label) {
		entries.unshift({ type: 'separator', label });
236 237
	}
	return entries;
238 239
}

240 241
class GenerateColorThemeAction extends Action {

242
	static readonly ID = 'workbench.action.generateColorTheme';
243
	static readonly LABEL = localize('generateColorTheme.label', "Generate Color Theme From Current Settings");
244 245 246 247

	constructor(
		id: string,
		label: string,
248 249
		@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
		@IEditorService private readonly editorService: IEditorService,
250 251 252 253
	) {
		super(id, label);
	}

J
Johannes Rieken 已提交
254
	run(): Promise<any> {
255
		let theme = this.themeService.getColorTheme();
256 257
		let colors = Registry.as<IColorRegistry>(ColorRegistryExtensions.ColorContribution).getColors();
		let colorIds = colors.map(c => c.id).sort();
258
		let resultingColors: { [key: string]: string } = {};
M
Matt Bierner 已提交
259
		let inherited: string[] = [];
260
		for (let colorId of colorIds) {
M
Matt Bierner 已提交
261
			const color = theme.getColor(colorId, false);
262
			if (color) {
263 264 265
				resultingColors[colorId] = Color.Format.CSS.formatHexA(color, true);
			} else {
				inherited.push(colorId);
266
			}
267 268
		}
		for (let id of inherited) {
M
Matt Bierner 已提交
269
			const color = theme.getColor(id);
270 271 272 273
			if (color) {
				resultingColors['__' + id] = Color.Format.CSS.formatHexA(color, true);
			}
		}
274
		let contents = JSON.stringify({
275
			'$schema': colorThemeSchemaId,
276 277
			type: theme.type,
			colors: resultingColors,
278
			tokenColors: theme.tokenColors.filter(t => !!t.scope)
279
		}, null, '\t');
280 281
		contents = contents.replace(/\"__/g, '//"');

282
		return this.editorService.openEditor({ contents, mode: 'jsonc' });
283 284 285
	}
}

J
Joao Moreno 已提交
286
const category = localize('preferences', "Preferences");
287

C
Christof Marti 已提交
288
const colorThemeDescriptor = new SyncActionDescriptor(SelectColorThemeAction, SelectColorThemeAction.ID, SelectColorThemeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_T) });
289 290 291 292
Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions).registerWorkbenchAction(colorThemeDescriptor, 'Preferences: Color Theme', category);

const iconThemeDescriptor = new SyncActionDescriptor(SelectIconThemeAction, SelectIconThemeAction.ID, SelectIconThemeAction.LABEL);
Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions).registerWorkbenchAction(iconThemeDescriptor, 'Preferences: File Icon Theme', category);
293 294 295 296 297 298


const developerCategory = localize('developer', "Developer");

const generateColorThemeDescriptor = new SyncActionDescriptor(GenerateColorThemeAction, GenerateColorThemeAction.ID, GenerateColorThemeAction.LABEL);
Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions).registerWorkbenchAction(generateColorThemeDescriptor, 'Developer: Generate Color Theme From Current Settings', developerCategory);
I
isidor 已提交
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316

MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
	group: '4_themes',
	command: {
		id: SelectColorThemeAction.ID,
		title: localize({ key: 'miSelectColorTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme")
	},
	order: 1
});

MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
	group: '4_themes',
	command: {
		id: SelectIconThemeAction.ID,
		title: localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme")
	},
	order: 2
});
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333

MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
	group: '4_themes',
	command: {
		id: SelectColorThemeAction.ID,
		title: localize('selectTheme.label', "Color Theme")
	},
	order: 1
});

MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
	group: '4_themes',
	command: {
		id: SelectIconThemeAction.ID,
		title: localize('themes.selectIconTheme.label', "File Icon Theme")
	},
	order: 2
334
});