themes.contribution.ts 12.1 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 62 63 64 65 66 67 68 69 70
					selectThemeTimeout = undefined;

					let themeId = theme.id;
					if (typeof theme.id === 'undefined') { // 'pick in marketplace' entry
						if (applyTheme) {
							openExtensionViewlet(this.viewletService, 'category:themes ');
						}
						themeId = currentTheme.id;
					}
					let target: ConfigurationTarget | undefined = undefined;
C
Christof Marti 已提交
71
					if (applyTheme) {
72 73
						let confValue = this.configurationService.inspect(COLOR_THEME_SETTING);
						target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER;
C
Christof Marti 已提交
74
					}
M
Martin Aeschlimann 已提交
75

76 77 78 79 80 81 82
					this.themeService.setColorTheme(themeId, target).then(undefined,
						err => {
							onUnexpectedError(err);
							this.themeService.setColorTheme(currentTheme.id, undefined);
						}
					);
				}, applyTheme ? 0 : 200);
J
Joao Moreno 已提交
83
			};
E
Erich Gamma 已提交
84

85
			const placeHolder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)");
86 87
			const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id);
			const activeItem: ThemeItem = picks[autoFocusIndex] as ThemeItem;
88 89 90

			const chooseTheme = (theme: ThemeItem) => selectTheme(theme || currentTheme, true);
			const tryTheme = (theme: ThemeItem) => selectTheme(theme, false);
E
Erich Gamma 已提交
91

92
			return this.quickInputService.pick(picks, { placeHolder, activeItem, onDidFocus: tryTheme })
93
				.then(chooseTheme);
E
Erich Gamma 已提交
94 95 96 97
		});
	}
}

98 99
class SelectIconThemeAction extends Action {

100
	static readonly ID = 'workbench.action.selectIconTheme';
101
	static readonly LABEL = localize('selectIconTheme.label', "File Icon Theme");
102 103 104 105

	constructor(
		id: string,
		label: string,
106 107 108 109
		@IQuickInputService private readonly quickInputService: IQuickInputService,
		@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
		@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
		@IViewletService private readonly viewletService: IViewletService,
110
		@IConfigurationService private readonly configurationService: IConfigurationService
M
Martin Aeschlimann 已提交
111

112 113 114 115
	) {
		super(id, label);
	}

J
Johannes Rieken 已提交
116
	run(): Promise<void> {
117
		return this.themeService.getFileIconThemes().then(themes => {
M
Martin Aeschlimann 已提交
118
			const currentTheme = this.themeService.getFileIconTheme();
119

120
			let picks: QuickPickInput<ThemeItem>[] = [{ id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable file icons') }];
121 122
			picks = picks.concat(
				toEntries(themes),
C
Christof Marti 已提交
123
				configurationEntries(this.extensionGalleryService, localize('installIconThemes', "Install Additional File Icon Themes..."))
124
			);
125

M
Martin Aeschlimann 已提交
126
			let selectThemeTimeout: number | undefined;
127

128
			const selectTheme = (theme: ThemeItem, applyTheme: boolean) => {
129 130
				if (selectThemeTimeout) {
					clearTimeout(selectThemeTimeout);
131
				}
M
Martin Aeschlimann 已提交
132
				selectThemeTimeout = window.setTimeout(() => {
133 134 135 136 137 138 139 140 141 142 143 144
					selectThemeTimeout = undefined;
					let themeId = theme.id;
					if (typeof theme.id === 'undefined') { // 'pick in marketplace' entry
						if (applyTheme) {
							openExtensionViewlet(this.viewletService, 'tag:icon-theme ');
						}
						themeId = currentTheme.id;
					}
					let target: ConfigurationTarget | undefined = undefined;
					if (applyTheme) {
						let confValue = this.configurationService.inspect(ICON_THEME_SETTING);
						target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER;
145
					}
146 147 148 149 150 151 152
					this.themeService.setFileIconTheme(themeId, target).then(undefined,
						err => {
							onUnexpectedError(err);
							this.themeService.setFileIconTheme(currentTheme.id, undefined);
						}
					);
				}, applyTheme ? 0 : 200);
153 154 155
			};

			const placeHolder = localize('themes.selectIconTheme', "Select File Icon Theme");
156 157
			const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id);
			const activeItem: ThemeItem = picks[autoFocusIndex] as ThemeItem;
158 159
			const chooseTheme = (theme: ThemeItem) => selectTheme(theme || currentTheme, true);
			const tryTheme = (theme: ThemeItem) => selectTheme(theme, false);
160

161
			return this.quickInputService.pick(picks, { placeHolder, activeItem, onDidFocus: tryTheme })
162
				.then(chooseTheme);
163 164 165 166
		});
	}
}

167
function configurationEntries(extensionGalleryService: IExtensionGalleryService, label: string): QuickPickInput<ThemeItem>[] {
168
	if (extensionGalleryService.isEnabled()) {
C
Christof Marti 已提交
169 170
		return [
			{
C
Christof Marti 已提交
171
				type: 'separator'
C
Christof Marti 已提交
172 173
			},
			{
R
Rob Lourens 已提交
174
				id: undefined,
C
Christof Marti 已提交
175
				label: label,
176
				alwaysShow: true
C
Christof Marti 已提交
177 178
			}
		];
179 180 181 182
	}
	return [];
}

C
Christof Marti 已提交
183 184
function openExtensionViewlet(viewletService: IViewletService, query: string) {
	return viewletService.openViewlet(VIEWLET_ID, true).then(viewlet => {
185 186 187 188
		if (viewlet) {
			(viewlet as IExtensionsViewlet).search(query);
			viewlet.focus();
		}
C
Christof Marti 已提交
189 190
	});
}
191 192 193 194 195 196 197 198
interface ThemeItem {
	id: string | undefined;
	label: string;
	description?: string;
	alwaysShow?: boolean;
}

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

202 203 204 205
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 已提交
206 207
	if (entries.length > 0 && label) {
		entries.unshift({ type: 'separator', label });
208 209
	}
	return entries;
210 211
}

212 213
class GenerateColorThemeAction extends Action {

214
	static readonly ID = 'workbench.action.generateColorTheme';
215
	static readonly LABEL = localize('generateColorTheme.label', "Generate Color Theme From Current Settings");
216 217 218 219

	constructor(
		id: string,
		label: string,
220 221
		@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
		@IEditorService private readonly editorService: IEditorService,
222 223 224 225
	) {
		super(id, label);
	}

J
Johannes Rieken 已提交
226
	run(): Promise<any> {
227
		let theme = this.themeService.getColorTheme();
228 229
		let colors = Registry.as<IColorRegistry>(ColorRegistryExtensions.ColorContribution).getColors();
		let colorIds = colors.map(c => c.id).sort();
230
		let resultingColors: { [key: string]: string } = {};
M
Matt Bierner 已提交
231
		let inherited: string[] = [];
232
		for (let colorId of colorIds) {
M
Matt Bierner 已提交
233
			const color = theme.getColor(colorId, false);
234
			if (color) {
235 236 237
				resultingColors[colorId] = Color.Format.CSS.formatHexA(color, true);
			} else {
				inherited.push(colorId);
238
			}
239 240
		}
		for (let id of inherited) {
M
Matt Bierner 已提交
241
			const color = theme.getColor(id);
242 243 244 245
			if (color) {
				resultingColors['__' + id] = Color.Format.CSS.formatHexA(color, true);
			}
		}
246
		let contents = JSON.stringify({
247
			'$schema': colorThemeSchemaId,
248 249
			type: theme.type,
			colors: resultingColors,
250
			tokenColors: theme.tokenColors.filter(t => !!t.scope)
251
		}, null, '\t');
252 253
		contents = contents.replace(/\"__/g, '//"');

254
		return this.editorService.openEditor({ contents, mode: 'jsonc' });
255 256 257
	}
}

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

C
Christof Marti 已提交
260
const colorThemeDescriptor = new SyncActionDescriptor(SelectColorThemeAction, SelectColorThemeAction.ID, SelectColorThemeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_T) });
261 262 263 264
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);
265 266 267 268 269 270


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 已提交
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288

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
});
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305

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
306
});