themes as settings

This commit is contained in:
Martin Aeschlimann 2017-02-13 17:08:48 +01:00
parent b7dc5cd88b
commit 255ecfb084
12 changed files with 288 additions and 154 deletions

View file

@ -9,26 +9,31 @@
"contributes": {
"themes": [
{
"id": "Default Dark+",
"label": "Dark+ (default dark)",
"uiTheme": "vs-dark",
"path": "./themes/dark_plus.json"
},
{
"id": "Default Light+",
"label": "Light+ (default light)",
"uiTheme": "vs",
"path": "./themes/light_plus.json"
},
{
"id": "Visual Studio Dark",
"label": "Dark (Visual Studio)",
"uiTheme": "vs-dark",
"path": "./themes/dark_vs.json"
},
{
"id": "Visual Studio Light",
"label": "Light (Visual Studio)",
"uiTheme": "vs",
"path": "./themes/light_vs.json"
},
{
"id": "Default High Contrast",
"label": "High Contrast",
"uiTheme": "hc-black",
"path": "./themes/hc_black.json"

View file

@ -9,7 +9,6 @@ import * as path from 'path';
import * as platform from 'vs/base/common/platform';
import * as objects from 'vs/base/common/objects';
import nls = require('vs/nls');
import { IStorageService } from 'vs/code/electron-main/storage';
import { shell, screen, BrowserWindow, systemPreferences, app } from 'electron';
import { TPromise, TValueCallback } from 'vs/base/common/winjs.base';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
@ -78,6 +77,7 @@ export interface IWindowConfiguration extends ParsedArgs {
zoomLevel?: number;
fullscreen?: boolean;
highContrast?: boolean;
baseTheme?: string;
accessibilitySupport?: boolean;
isInitialStartup?: boolean;
@ -134,8 +134,6 @@ export interface IVSCodeWindow {
export class VSCodeWindow implements IVSCodeWindow {
public static colorThemeStorageKey = 'theme';
private static MIN_WIDTH = 200;
private static MIN_HEIGHT = 120;
@ -162,8 +160,7 @@ export class VSCodeWindow implements IVSCodeWindow {
config: IWindowCreationOptions,
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IConfigurationService private configurationService: IConfigurationService,
@IStorageService private storageService: IStorageService
@IConfigurationService private configurationService: IConfigurationService
) {
this.options = config;
this._lastFocusTime = -1;
@ -177,9 +174,9 @@ export class VSCodeWindow implements IVSCodeWindow {
this.restoreWindowState(config.state);
// For VS theme we can show directly because background is white
const themeId = this.storageService.getItem<string>(VSCodeWindow.colorThemeStorageKey);
const usesLightTheme = /vs($| )/.test(themeId);
const usesHighContrastTheme = /hc-black($| )/.test(themeId) || (platform.isWindows && systemPreferences.isInvertedColorScheme());
const themeId = this.configurationService.lookup<string>('workbench.colorTheme').value;
const usesLightTheme = /^l-/.test(themeId);
const usesHighContrastTheme = /^hc-/.test(themeId) || (platform.isWindows && systemPreferences.isInvertedColorScheme());
// in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below)
const isFullscreenOrMaximized = (this.currentWindowMode === WindowMode.Maximized || this.currentWindowMode === WindowMode.Fullscreen);
@ -505,6 +502,16 @@ export class VSCodeWindow implements IVSCodeWindow {
windowConfiguration.highContrast = platform.isWindows && systemPreferences.isInvertedColorScheme() && (!windowConfig || windowConfig.autoDetectHighContrast);
windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();
// background color
const themeId = this.configurationService.lookup<string>('workbench.colorTheme').value;
if (themeId[0] === 'h') {
windowConfiguration.baseTheme = 'hc-black';
} else if (themeId[0] === 'l') {
windowConfiguration.baseTheme = 'vs';
} else {
windowConfiguration.baseTheme = 'vs-dark';
}
// Perf Counters
windowConfiguration.perfStartTime = global.perfStartTime;
windowConfiguration.perfAppReady = global.perfAppReady;

View file

@ -289,13 +289,7 @@ export class WindowsManager implements IWindowsMainService {
}
private onBroadcast(event: string, payload: any): void {
// Theme changes
if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
this.storageService.setItem(VSCodeWindow.colorThemeStorageKey, payload);
}
}
public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
// Only reload when the window has not vetoed this
@ -530,7 +524,7 @@ export class WindowsManager implements IWindowsMainService {
const mru = this.getRecentPathsList();
paths.forEach(p => {
const {path, isFile} = p;
const { path, isFile } = p;
if (isFile) {
mru.files.unshift(path);
@ -794,8 +788,7 @@ export class WindowsManager implements IWindowsMainService {
},
this.logService,
this.environmentService,
this.configurationService,
this.storageService
this.configurationService
);
WindowsManager.WINDOWS.push(vscodeWindow);

View file

@ -16,13 +16,8 @@
<script>
(function() {
try {
var theme = window.localStorage.getItem('storage://global/workbench.theme');
if (theme) {
var baseTheme = theme.split(' ')[0];
if (baseTheme !== 'vs-dark') {
window.document.body.className = 'monaco-shell ' + baseTheme;
}
}
let config = JSON.parse(decodeURIComponent(window.location.search.substring(8)));
window.document.body.className = 'monaco-shell ' + config.baseTheme;
} catch (error) {
console.error(error);
}

View file

@ -269,7 +269,7 @@ export class ElectronWindow {
const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
if (windowConfig && windowConfig.autoDetectHighContrast) {
this.partService.joinCreation().then(() => {
this.themeService.setColorTheme(VS_HC_THEME, false);
this.themeService.setColorTheme(VS_HC_THEME, null);
});
}
});
@ -278,7 +278,7 @@ export class ElectronWindow {
const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
if (windowConfig && windowConfig.autoDetectHighContrast) {
this.partService.joinCreation().then(() => {
this.themeService.setColorTheme(VS_DARK_THEME, false);
this.themeService.setColorTheme(VS_DARK_THEME, null);
});
}
});

View file

@ -11,7 +11,6 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { addDisposableListener, addClass } from 'vs/base/browser/dom';
import { isLightTheme, isDarkTheme } from 'vs/platform/theme/common/themes';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { MenuRegistry } from 'vs/platform/actions/common/actions';
import { IColorTheme } from 'vs/workbench/services/themes/common/themeService';
@ -154,7 +153,6 @@ export default class Webview {
}
style(theme: IColorTheme): void {
let themeId = theme.id;
const {color, backgroundColor, fontFamily, fontWeight, fontSize} = window.getComputedStyle(this._styleElement);
let value = `
@ -193,7 +191,7 @@ export default class Webview {
let activeTheme: ApiThemeClassName;
if (isLightTheme(themeId)) {
if (theme.isLightTheme()) {
value += `
::-webkit-scrollbar-thumb {
background-color: rgba(100, 100, 100, 0.4);
@ -207,7 +205,7 @@ export default class Webview {
activeTheme = 'vscode-light';
} else if (isDarkTheme(themeId)) {
} else if (theme.isDarkTheme()) {
value += `
::-webkit-scrollbar-thumb {
background-color: rgba(121, 121, 121, 0.4);

View file

@ -31,7 +31,6 @@ import { createActionItem } from 'vs/platform/actions/browser/menuItemActionItem
import { SCMMenus } from './scmMenus';
import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
import { IThemeService } from 'vs/workbench/services/themes/common/themeService';
import { isDarkTheme } from 'vs/platform/theme/common/themes';
import { SCMEditor } from './scmEditor';
import { IModelService } from 'vs/editor/common/services/modelService';
@ -128,7 +127,7 @@ class ResourceRenderer implements IRenderer<ISCMResource, ResourceTemplate> {
toggleClass(template.name, 'strike-through', resource.decorations.strikeThrough);
const theme = this.themeService.getColorTheme();
const icon = isDarkTheme(theme.id) ? resource.decorations.iconDark : resource.decorations.icon;
const icon = theme.isDarkTheme() ? resource.decorations.iconDark : resource.decorations.icon;
if (icon) {
template.decorationIcon.style.backgroundImage = `url('${icon}')`;

View file

@ -20,7 +20,6 @@ import { KillTerminalAction, CreateNewTerminalAction, SwitchTerminalInstanceActi
import { Panel } from 'vs/workbench/browser/panel';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { TPromise } from 'vs/base/common/winjs.base';
import { getBaseThemeId } from 'vs/platform/theme/common/themes';
export class TerminalPanel extends Panel {
@ -204,8 +203,7 @@ export class TerminalPanel extends Panel {
if (!colorTheme) {
colorTheme = this._themeService.getColorTheme();
}
let themeId = colorTheme.id;
let baseThemeId = getBaseThemeId(themeId);
let baseThemeId = colorTheme.getBaseThemeId();
if (baseThemeId === this._currentBaseThemeId) {
return;
}

View file

@ -15,11 +15,13 @@ import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { Registry } from 'vs/platform/platform';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IThemeService } from 'vs/workbench/services/themes/common/themeService';
import { IThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING } from 'vs/workbench/services/themes/common/themeService';
import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { Delayer } from 'vs/base/common/async';
import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
export class SelectColorThemeAction extends Action {
@ -33,7 +35,8 @@ export class SelectColorThemeAction extends Action {
@IMessageService private messageService: IMessageService,
@IThemeService private themeService: IThemeService,
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService,
@IViewletService private viewletService: IViewletService
@IViewletService private viewletService: IViewletService,
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService
) {
super(id, label);
}
@ -48,12 +51,18 @@ export class SelectColorThemeAction extends Action {
.map(theme => ({ id: theme.id, label: theme.label, description: theme.description }))
.sort((t1, t2) => t1.label.localeCompare(t2.label));
const selectTheme = (theme, broadcast) => {
const selectTheme = (theme, applyTheme) => {
if (theme === pickInMarketPlace) {
theme = currentTheme;
}
this.themeService.setColorTheme(theme.id, broadcast)
.done(null, err => this.messageService.show(Severity.Info, localize('problemChangingTheme', "Problem loading theme: {0}", err)));
let target = null;
if (applyTheme) {
let confValue = this.configurationService.lookup(COLOR_THEME_SETTING);
target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER;
}
this.themeService.setColorTheme(theme.id, target)
.done(null, err => this.messageService.show(Severity.Info, localize('problemChangingTheme', "Problem setting theme: {0}", err)));
};
const placeHolder = localize('themes.selectTheme', "Select Color Theme");
@ -86,7 +95,9 @@ class SelectIconThemeAction extends Action {
@IMessageService private messageService: IMessageService,
@IThemeService private themeService: IThemeService,
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService,
@IViewletService private viewletService: IViewletService
@IViewletService private viewletService: IViewletService,
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService
) {
super(id, label);
}
@ -103,12 +114,17 @@ class SelectIconThemeAction extends Action {
picks.splice(0, 0, { id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable file icons') });
const selectTheme = (theme, broadcast) => {
const selectTheme = (theme, applyTheme) => {
if (theme === pickInMarketPlace) {
theme = currentTheme;
}
this.themeService.setFileIconTheme(theme && theme.id, broadcast)
.done(null, err => this.messageService.show(Severity.Info, localize('problemChangingIconTheme', "Problem loading icon theme: {0}", err.message)));
let target = null;
if (applyTheme) {
let confValue = this.configurationService.lookup(ICON_THEME_SETTING);
target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER;
}
this.themeService.setFileIconTheme(theme && theme.id, target)
.done(null, err => this.messageService.show(Severity.Info, localize('problemChangingIconTheme', "Problem setting icon theme: {0}", err.message)));
};
const placeHolder = localize('themes.selectIconTheme', "Select File Icon Theme");

View file

@ -183,7 +183,7 @@ class Snapper {
let defaultThemes = themeDatas.filter(themeData => !!getThemeName(themeData.id));
return TPromise.join(defaultThemes.map(defaultTheme => {
let themeId = defaultTheme.id;
return this.themeService.setColorTheme(themeId, false).then(success => {
return this.themeService.setColorTheme(themeId, null).then(success => {
if (success) {
let themeName = getThemeName(themeId);
result[themeName] = {
@ -194,7 +194,7 @@ class Snapper {
});
}));
}).then(_ => {
return this.themeService.setColorTheme(currentTheme.id, false).then(_ => {
return this.themeService.setColorTheme(currentTheme.id, null).then(_ => {
return result;
});
});

View file

@ -7,6 +7,7 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
import Event from 'vs/base/common/event';
import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
export let IThemeService = createDecorator<IThemeService>('themeService');
@ -14,17 +15,27 @@ export const VS_LIGHT_THEME = 'vs';
export const VS_DARK_THEME = 'vs-dark';
export const VS_HC_THEME = 'hc-black';
export const COLOR_THEME_SETTING = 'workbench.colorTheme';
export const ICON_THEME_SETTING = 'workbench.iconTheme';
export interface IColorTheme {
readonly id: string;
readonly label: string;
readonly settingsId: string;
readonly description?: string;
readonly isLoaded: boolean;
readonly settings?: IThemeSetting[];
isLightTheme(): boolean;
isDarkTheme(): boolean;
getSyntaxThemeId(): string;
getBaseThemeId(): string;
}
export interface IFileIconTheme {
readonly id: string;
readonly label: string;
readonly settingsId: string;
readonly description?: string;
readonly isLoaded: boolean;
readonly hasFileIcons?: boolean;
@ -33,12 +44,12 @@ export interface IFileIconTheme {
export interface IThemeService {
_serviceBrand: any;
setColorTheme(themeId: string, broadcastToAllWindows: boolean): TPromise<IColorTheme>;
setColorTheme(themeId: string, settingsTarget: ConfigurationTarget): TPromise<IColorTheme>;
getColorTheme(): IColorTheme;
getColorThemes(): TPromise<IColorTheme[]>;
onDidColorThemeChange: Event<IColorTheme>;
setFileIconTheme(iconThemeId: string, broadcastToAllWindows: boolean): TPromise<IFileIconTheme>;
setFileIconTheme(iconThemeId: string, settingsTarget: ConfigurationTarget): TPromise<IFileIconTheme>;
getFileIconTheme(): IFileIconTheme;
getFileIconThemes(): TPromise<IFileIconTheme[]>;
onDidFileIconThemeChange: Event<IFileIconTheme>;

View file

@ -8,19 +8,23 @@ import { TPromise, Promise } from 'vs/base/common/winjs.base';
import nls = require('vs/nls');
import Paths = require('vs/base/common/paths');
import Json = require('vs/base/common/json');
import * as types from 'vs/base/common/types';
import { IThemeExtensionPoint } from 'vs/platform/theme/common/themeExtensionPoint';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry';
import { IThemeService, IThemeSetting, IColorTheme, IFileIconTheme, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/themeService';
import { IThemeService, IThemeSetting, IColorTheme, IFileIconTheme, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING } from 'vs/workbench/services/themes/common/themeService';
import { EditorStylesContribution, SearchViewStylesContribution, TerminalStylesContribution } from 'vs/workbench/services/themes/electron-browser/stylesContributions';
import { getBaseThemeId } from 'vs/platform/theme/common/themes';
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
import { getBaseThemeId, getSyntaxThemeId, isDarkTheme, isLightTheme } from 'vs/platform/theme/common/themes';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Registry } from 'vs/platform/platform';
import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import errors = require('vs/base/common/errors');
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';
import { $ } from 'vs/base/browser/builder';
import Event, { Emitter } from 'vs/base/common/event';
@ -31,13 +35,9 @@ import pfs = require('vs/base/node/pfs');
// implementation
const DEFAULT_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json';
const DEFAULT_THEME_NAME = 'd-Dark+ (default dark)';
const COLOR_THEME_CHANNEL = 'vscode:changeColorTheme';
const ICON_THEME_CHANNEL = 'vscode:changeIconTheme';
const COLOR_THEME_PREF = 'workbench.theme';
const ICON_THEME_PREF = 'workbench.iconTheme';
let defaultBaseTheme = getBaseThemeId(DEFAULT_THEME_ID);
const defaultBaseTheme = getBaseThemeId(DEFAULT_THEME_ID);
const defaultThemeExtensionId = 'vscode-theme-defaults';
const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults';
@ -61,8 +61,12 @@ let themesExtPoint = ExtensionsRegistry.registerExtensionPoint<IThemeExtensionPo
type: 'array',
items: {
type: 'object',
defaultSnippets: [{ body: { label: '${1:label}', uiTheme: VS_DARK_THEME, path: './themes/${2:id}.tmTheme.' } }],
defaultSnippets: [{ body: { label: '${1:label}', id: '${2:id}', uiTheme: VS_DARK_THEME, path: './themes/${3:id}.tmTheme.' } }],
properties: {
id: {
description: nls.localize('vscode.extension.contributes.themes.id', 'Id of the icon theme as used in the user settings.'),
type: 'string'
},
label: {
description: nls.localize('vscode.extension.contributes.themes.label', 'Label of the color theme as shown in the UI.'),
type: 'string'
@ -104,20 +108,38 @@ let iconThemeExtPoint = ExtensionsRegistry.registerExtensionPoint<IThemeExtensio
}
});
class IInternalColorThemeData implements IColorTheme {
class ColorThemeData implements IColorTheme {
id: string;
label: string;
settingsId: string;
description?: string;
settings?: IThemeSetting[];
isLoaded: boolean;
path?: string;
styleSheetContent?: string;
extensionData: ExtensionData;
isLightTheme() {
return isLightTheme(this.id);
}
isDarkTheme() {
return isDarkTheme(this.id);
}
getSyntaxThemeId() {
return getSyntaxThemeId(this.id);
}
getBaseThemeId() {
return getBaseThemeId(this.id);
}
}
class IInternalIconThemeData implements IFileIconTheme {
interface IInternalIconThemeData extends IFileIconTheme {
id: string;
label: string;
settingsId: string;
description?: string;
hasFileIcons?: boolean;
hasFolderIcons?: boolean;
@ -171,6 +193,7 @@ interface ExtensionData {
const noFileIconTheme: IFileIconTheme = {
id: '',
label: '',
settingsId: null,
hasFileIcons: false,
hasFolderIcons: false,
isLoaded: true
@ -197,10 +220,16 @@ let defaultThemeColors: { [baseTheme: string]: IThemeSetting[] } = {
],
};
const settingsIdPrefix = {
[VS_DARK_THEME]: 'd-',
[VS_LIGHT_THEME]: 'l-',
[VS_HC_THEME]: 'hc-',
};
export class ThemeService implements IThemeService {
_serviceBrand: any;
private knownColorThemes: IInternalColorThemeData[];
private knownColorThemes: ColorThemeData[];
private currentColorTheme: IColorTheme;
private container: HTMLElement;
private onColorThemeChange: Emitter<IColorTheme>;
@ -212,8 +241,9 @@ export class ThemeService implements IThemeService {
constructor(
container: HTMLElement,
@IExtensionService private extensionService: IExtensionService,
@IWindowIPCService private windowService: IWindowIPCService,
@IStorageService private storageService: IStorageService,
@IConfigurationService private configurationService: IConfigurationService,
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService,
@ITelemetryService private telemetryService: ITelemetryService) {
this.container = container;
@ -225,23 +255,26 @@ export class ThemeService implements IThemeService {
let isLightTheme = (Array.prototype.indexOf.call(document.body.classList, 'vs') >= 0);
let foreground = isLightTheme ? '#000000' : '#D4D4D4';
let background = isLightTheme ? '#ffffff' : '#1E1E1E';
this.currentColorTheme = {
id: this.storageService.get(COLOR_THEME_PREF, StorageScope.GLOBAL, DEFAULT_THEME_ID),
label: '',
isLoaded: false,
settings: [{
settings: {
foreground: foreground,
background: background
}
}]
};
let initialTheme = new ColorThemeData();
initialTheme.id = isLightTheme ? VS_LIGHT_THEME : VS_DARK_THEME;
initialTheme.label = '';
initialTheme.settingsId = null;
initialTheme.isLoaded = false;
initialTheme.settings = [{
settings: {
foreground: foreground,
background: background
}
}];
this.currentColorTheme = initialTheme;
this.onColorThemeChange = new Emitter<IColorTheme>();
this.knownIconThemes = [];
this.currentIconTheme = {
id: '',
label: '',
settingsId: null,
isLoaded: false,
hasFileIcons: false,
hasFolderIcons: false
@ -260,12 +293,6 @@ export class ThemeService implements IThemeService {
}
});
windowService.onBroadcast(e => {
if (e.channel === COLOR_THEME_CHANNEL && typeof e.payload === 'string') {
this.setColorTheme(e.payload, false);
}
});
iconThemeExtPoint.setHandler((extensions) => {
for (let ext of extensions) {
let extensionData = {
@ -278,9 +305,21 @@ export class ThemeService implements IThemeService {
}
});
windowService.onBroadcast(e => {
if (e.channel === ICON_THEME_CHANNEL && typeof e.payload === 'string') {
this.setFileIconTheme(e.payload, false);
this.configurationService.onDidUpdateConfiguration(e => {
let colorThemeSetting = this.configurationService.lookup<string>(COLOR_THEME_SETTING).value;
if (colorThemeSetting !== this.currentColorTheme.settingsId) {
this.findThemeDataBySettingsId(colorThemeSetting, null).then(theme => {
if (theme) {
this.setColorTheme(theme.id, null);
}
});
}
let iconThemeSetting = this.configurationService.lookup<string>(ICON_THEME_SETTING).value || '';
if (iconThemeSetting !== this.currentIconTheme.settingsId) {
this.findIconThemeBySettingsId(iconThemeSetting).then(theme => {
this.setFileIconTheme(theme && theme.id, null);
});
}
});
@ -296,33 +335,52 @@ export class ThemeService implements IThemeService {
}
private initialize(): TPromise<void> {
let themeId = this.storageService.get(COLOR_THEME_PREF, StorageScope.GLOBAL, null);
if (!themeId) {
themeId = DEFAULT_THEME_ID;
this.storageService.store(COLOR_THEME_PREF, themeId, StorageScope.GLOBAL);
}
let iconThemeId = this.storageService.get(ICON_THEME_PREF, StorageScope.GLOBAL, null);
return Promise.join([
this.setColorTheme(themeId, false),
this.setFileIconTheme(iconThemeId, false)
]);
let legacyColorThemeId = this.storageService.get('workbench.theme', StorageScope.GLOBAL, null);
let legacyIconThemeId = this.storageService.get('workbench.iconTheme', StorageScope.GLOBAL, null);
if (legacyColorThemeId || legacyIconThemeId) {
this.storageService.remove('workbench.theme', StorageScope.GLOBAL);
this.storageService.remove('workbench.iconTheme', StorageScope.GLOBAL);
return Promise.join([
this.findThemeData(legacyColorThemeId, DEFAULT_THEME_ID).then(theme => {
let themeId = theme ? theme.id : DEFAULT_THEME_ID;
let target = themeId !== DEFAULT_THEME_ID ? ConfigurationTarget.USER : null;
return this.setColorTheme(themeId, target);
}),
this._findIconThemeData(legacyIconThemeId).then(theme => {
let themeId = theme && theme.id;
let target = themeId !== DEFAULT_THEME_ID ? ConfigurationTarget.USER : null;
return this.setFileIconTheme(themeId, target);
})
]);
}
let colorThemeSetting = this.configurationService.lookup<string>(COLOR_THEME_SETTING).value;
let iconThemeSetting = this.configurationService.lookup<string>(ICON_THEME_SETTING).value || '';
return Promise.join([
this.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => {
return this.setColorTheme(theme && theme.id, null);
}),
this.findIconThemeBySettingsId(iconThemeSetting).then(theme => {
return this.setFileIconTheme(theme && theme.id, null);
}),
]);
}
public setColorTheme(themeId: string, broadcastToAllWindows: boolean): TPromise<IColorTheme> {
public setColorTheme(themeId: string, settingsTarget: ConfigurationTarget): TPromise<IColorTheme> {
if (!themeId) {
return TPromise.as(null);
}
if (themeId === this.currentColorTheme.id && this.currentColorTheme.isLoaded) {
if (broadcastToAllWindows) {
this.windowService.broadcast({ channel: COLOR_THEME_CHANNEL, payload: themeId });
}
return TPromise.as(this.currentColorTheme);
return this.writeColorThemeConfiguration(settingsTarget);
}
themeId = validateThemeId(themeId); // migrate theme ids
let onApply = (newTheme: IInternalColorThemeData) => {
let onApply = (newTheme: ColorThemeData) => {
let newThemeId = newTheme.id;
if (this.container) {
if (this.currentColorTheme) {
@ -332,45 +390,61 @@ export class ThemeService implements IThemeService {
}
this.currentColorTheme = newTheme;
this.storageService.store(COLOR_THEME_PREF, newThemeId, StorageScope.GLOBAL);
if (broadcastToAllWindows) {
this.windowService.broadcast({ channel: COLOR_THEME_CHANNEL, payload: newThemeId });
} else {
this.sendTelemetry(newTheme.id, newTheme.extensionData, 'color');
}
this.sendTelemetry(newTheme.id, newTheme.extensionData, 'color');
this.onColorThemeChange.fire(this.currentColorTheme);
return this.currentColorTheme;
return this.writeColorThemeConfiguration(settingsTarget);
};
return this.applyThemeCSS(themeId, DEFAULT_THEME_ID, onApply);
return this.findThemeData(themeId, DEFAULT_THEME_ID).then(themeData => {
if (themeData) {
return applyTheme(themeData, onApply);
}
return null;
});
}
private writeColorThemeConfiguration(settingsTarget: ConfigurationTarget) {
if (!types.isUndefinedOrNull(settingsTarget)) {
return this.configurationEditingService.writeConfiguration(settingsTarget, { key: COLOR_THEME_SETTING, value: this.currentColorTheme.settingsId }).then(_ => {
return this.currentColorTheme;
});
}
return TPromise.as(this.currentColorTheme);
}
public getColorTheme() {
return this.currentColorTheme;
}
private findThemeData(themeId: string, defaultId?: string): TPromise<IInternalColorThemeData> {
private findThemeData(themeId: string, defaultId?: string): TPromise<ColorThemeData> {
return this.getColorThemes().then(allThemes => {
let themes = allThemes.filter(t => t.id === themeId);
if (themes.length > 0) {
return <IInternalColorThemeData>themes[0];
}
if (defaultId) {
let themes = allThemes.filter(t => t.id === defaultId);
if (themes.length > 0) {
return <IInternalColorThemeData>themes[0];
let defaultTheme = void 0;
for (let t of allThemes) {
if (t.id === themeId) {
return t;
}
if (t.id === defaultId) {
defaultTheme = t;
}
}
return null;
return defaultTheme;
});
}
private applyThemeCSS(themeId: string, defaultId: string, onApply: (theme: IInternalColorThemeData) => IColorTheme): TPromise<IColorTheme> {
return this.findThemeData(themeId, defaultId).then(theme => {
if (theme) {
return applyTheme(theme, onApply);
private findThemeDataBySettingsId(settingsId: string, defaultId: string): TPromise<ColorThemeData> {
return this.getColorThemes().then(allThemes => {
let defaultTheme = void 0;
for (let t of allThemes) {
if (t.settingsId === settingsId) {
return t;
}
if (t.id === defaultId) {
defaultTheme = t;
}
}
return null;
return defaultTheme;
});
}
@ -390,7 +464,7 @@ export class ThemeService implements IThemeService {
return;
}
themes.forEach(theme => {
if (!theme.path || (typeof theme.path !== 'string')) {
if (!theme.path || !types.isString(theme.path)) {
collector.error(nls.localize(
'reqpath',
"Expected string in `contributes.{0}.path`. Provided value: {1}",
@ -405,15 +479,19 @@ export class ThemeService implements IThemeService {
collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", themesExtPoint.name, normalizedAbsolutePath, extensionFolderPath));
}
let baseTheme = theme.uiTheme || defaultBaseTheme;
let themeSelector = toCSSSelector(extensionData.extensionId + '-' + Paths.normalize(theme.path));
this.knownColorThemes.push({
id: `${theme.uiTheme || defaultBaseTheme} ${themeSelector}`,
label: theme.label || Paths.basename(theme.path),
description: theme.description,
path: normalizedAbsolutePath,
extensionData: extensionData,
isLoaded: false
});
let themeData = new ColorThemeData();
themeData.id = `${baseTheme} ${themeSelector}`;
themeData.label = theme.label || Paths.basename(theme.path);
themeData.settingsId = settingsIdPrefix[baseTheme] + (theme.id || themeData.label);
themeData.description = theme.description;
themeData.path = normalizedAbsolutePath;
themeData.extensionData = extensionData;
themeData.isLoaded = false;
this.knownColorThemes.push(themeData);
});
}
@ -427,7 +505,7 @@ export class ThemeService implements IThemeService {
return;
}
iconThemes.forEach(iconTheme => {
if (!iconTheme.path || (typeof iconTheme.path !== 'string')) {
if (!iconTheme.path || !types.isString(iconTheme.path)) {
collector.error(nls.localize(
'reqpath',
"Expected string in `contributes.{0}.path`. Provided value: {1}",
@ -436,7 +514,7 @@ export class ThemeService implements IThemeService {
));
return;
}
if (!iconTheme.id || (typeof iconTheme.id !== 'string')) {
if (!iconTheme.id || !types.isString(iconTheme.id)) {
collector.error(nls.localize(
'reqid',
"Expected string in `contributes.{0}.id`. Provided value: {1}",
@ -454,6 +532,7 @@ export class ThemeService implements IThemeService {
this.knownIconThemes.push({
id: extensionData.extensionId + '-' + iconTheme.id,
label: iconTheme.label || Paths.basename(iconTheme.path),
settingsId: iconTheme.id,
description: iconTheme.description,
path: normalizedAbsolutePath,
extensionData: extensionData,
@ -487,13 +566,10 @@ export class ThemeService implements IThemeService {
return this.currentIconTheme;
}
public setFileIconTheme(iconTheme: string, broadcastToAllWindows: boolean): TPromise<IColorTheme> {
public setFileIconTheme(iconTheme: string, settingsTarget: ConfigurationTarget): TPromise<IFileIconTheme> {
iconTheme = iconTheme || '';
if (iconTheme === this.currentIconTheme.id && this.currentIconTheme.isLoaded) {
if (broadcastToAllWindows) {
this.windowService.broadcast({ channel: ICON_THEME_CHANNEL, payload: iconTheme });
}
return TPromise.as(this.currentIconTheme);
return this.writeFileIconConfiguration(settingsTarget);
}
let onApply = (newIconTheme: IInternalIconThemeData) => {
if (newIconTheme) {
@ -501,6 +577,7 @@ export class ThemeService implements IThemeService {
} else {
this.currentIconTheme = noFileIconTheme;
}
if (this.container) {
if (newIconTheme) {
$(this.container).addClass(fileIconsEnabledClass);
@ -508,36 +585,51 @@ export class ThemeService implements IThemeService {
$(this.container).removeClass(fileIconsEnabledClass);
}
}
this.storageService.store(ICON_THEME_PREF, this.currentIconTheme.id, StorageScope.GLOBAL);
if (broadcastToAllWindows) {
this.windowService.broadcast({ channel: ICON_THEME_CHANNEL, payload: this.currentIconTheme.id });
} else if (newIconTheme) {
if (newIconTheme) {
this.sendTelemetry(newIconTheme.id, newIconTheme.extensionData, 'fileIcon');
}
this.onFileIconThemeChange.fire(this.currentIconTheme);
return this.currentIconTheme;
return this.writeFileIconConfiguration(settingsTarget);
};
return this._updateIconTheme(iconTheme, onApply);
return this._findIconThemeData(iconTheme).then(iconThemeData => {
return _applyIconTheme(iconThemeData, onApply);
});
}
private _updateIconTheme(iconTheme: string, onApply: (theme: IInternalIconThemeData) => IFileIconTheme): TPromise<IFileIconTheme> {
private writeFileIconConfiguration(settingsTarget: ConfigurationTarget): TPromise<IFileIconTheme> {
if (!types.isUndefinedOrNull(settingsTarget)) {
return this.configurationEditingService.writeConfiguration(settingsTarget, { key: ICON_THEME_SETTING, value: this.currentIconTheme.settingsId }).then(_ => {
return this.currentIconTheme;
});
}
return TPromise.as(this.currentIconTheme);
}
private _findIconThemeData(iconTheme: string): TPromise<IInternalIconThemeData> {
return this.getFileIconThemes().then(allIconSets => {
let iconSetData: IInternalIconThemeData;
for (let iconSet of allIconSets) {
if (iconSet.id === iconTheme) {
iconSetData = <IInternalIconThemeData>iconSet;
break;
return <IInternalIconThemeData>iconSet;
}
}
return _applyIconTheme(iconSetData, onApply);
return null;
});
}
private findIconThemeBySettingsId(settingsId: string): TPromise<IFileIconTheme> {
return this.getFileIconThemes().then(allIconSets => {
for (let iconSet of allIconSets) {
if (iconSet.settingsId === settingsId) {
return iconSet;
}
}
return null;
});
}
}
function _applyIconTheme(data: IInternalIconThemeData, onApply: (theme: IInternalIconThemeData) => IFileIconTheme): TPromise<IFileIconTheme> {
function _applyIconTheme(data: IInternalIconThemeData, onApply: (theme: IInternalIconThemeData) => TPromise<IFileIconTheme>): TPromise<IFileIconTheme> {
if (!data) {
_applyRules('', iconThemeRulesClassName);
return TPromise.as(onApply(data));
@ -728,7 +820,7 @@ function toCSSSelector(str: string) {
return str;
}
function applyTheme(theme: IInternalColorThemeData, onApply: (theme: IInternalColorThemeData) => IColorTheme): TPromise<IColorTheme> {
function applyTheme(theme: ColorThemeData, onApply: (theme: ColorThemeData) => TPromise<IColorTheme>): TPromise<IColorTheme> {
if (theme.styleSheetContent) {
_applyRules(theme.styleSheetContent, colorThemeRulesClassName);
return TPromise.as(onApply(theme));
@ -755,7 +847,7 @@ function _loadThemeDocument(baseTheme: string, themePath: string): TPromise<IThe
return TPromise.wrapError(new Error(nls.localize('error.cannotparsejson', "Problems parsing JSON theme file: {0}", errors.map(e => Json.getParseErrorMessage(e.error)).join(', '))));
}
if (!Array.isArray(contentValue.settings)) {
return TPromise.wrapError(new Error(nls.localize('error.invalidformat', "Problem parsing JSON theme file: {0}. 'settings' is not array.")));
return TPromise.wrapError(new Error(nls.localize({ key: 'error.invalidformat', comment: ['{0} will be replaced by a path. "settings" must not be translated.'] }, "Problem parsing JSON theme file: {0}. 'settings' is not array.")));
}
allSettings = allSettings.concat(contentValue.settings); // will clone
if (contentValue.include) {
@ -1013,4 +1105,24 @@ const schema: IJSONSchema = {
};
let schemaRegistry = <IJSONContributionRegistry>Registry.as(JSONExtensions.JSONContribution);
schemaRegistry.registerSchema(schemaId, schema);
schemaRegistry.registerSchema(schemaId, schema);
// Configuration: Workbench
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'workbench',
'order': 7.1,
'type': 'object',
'properties': {
[COLOR_THEME_SETTING]: {
'type': 'string',
'description': nls.localize('colorTheme', "Specifies the color theme used in the workbench."),
'default': DEFAULT_THEME_NAME
},
[ICON_THEME_SETTING]: {
'type': ['string', 'null'],
'default': null,
'description': nls.localize('iconTheme', "Specifies the icon theme used in the workbench.")
}
}
});