diff --git a/src/vs/workbench/services/themes/common/color.ts b/src/vs/workbench/services/themes/common/color.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea5328b19237e069e91eb30a51e9802a199b77e3 --- /dev/null +++ b/src/vs/workbench/services/themes/common/color.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface RGBA { r: number; g: number; b: number; a: number; } + +export class Color { + + private parsed: RGBA; + private str: string; + + constructor(arg: string | RGBA) { + if (typeof arg === 'string') { + this.parsed = Color.parse(arg); + } else { + this.parsed = arg; + } + this.str = null; + } + + private static parse(color: string): RGBA { + function parseHex(str: string) { + return parseInt('0x' + str); + } + + if (color.charAt(0) === '#' && color.length >= 7) { + let r = parseHex(color.substr(1, 2)); + let g = parseHex(color.substr(3, 2)); + let b = parseHex(color.substr(5, 2)); + let a = color.length === 9 ? parseHex(color.substr(7, 2)) / 0xff : 1; + return { r, g, b, a }; + } + return { r: 255, g: 0, b: 0, a: 1 }; + } + + public toString(): string { + if (!this.str) { + let p = this.parsed; + this.str = `rgba(${p.r}, ${p.g}, ${p.b}, ${+p.a.toFixed(2)})`; + } + return this.str; + } + + public transparent(factor: number): Color { + let p = this.parsed; + return new Color({ r: p.r, g: p.g, b: p.b, a: p.a * factor }); + } + + public opposite(): Color { + return new Color({ + r: 255 - this.parsed.r, + g: 255 - this.parsed.g, + b: 255 - this.parsed.b, + a : this.parsed.a + }); + } +} \ No newline at end of file diff --git a/src/vs/workbench/services/themes/common/themeService.ts b/src/vs/workbench/services/themes/common/themeService.ts index acd65a9b307f381658389a05b7b6d5fdfbcca2b2..a783b1433c3e2680e4f244b059b407ce911d7fd1 100644 --- a/src/vs/workbench/services/themes/common/themeService.ts +++ b/src/vs/workbench/services/themes/common/themeService.ts @@ -27,4 +27,19 @@ export interface IThemeData { label: string; description?: string; path: string; +} + +export interface IThemeDocument { + name: string; + include: string; + settings: IThemeSetting[]; +} + +export interface IThemeSetting { + name?: string; + scope?: string[]; + settings: IThemeSettingStyle[]; +} + +export interface IThemeSettingStyle { } \ No newline at end of file diff --git a/src/vs/workbench/services/themes/electron-browser/editorStyles.ts b/src/vs/workbench/services/themes/electron-browser/editorStyles.ts new file mode 100644 index 0000000000000000000000000000000000000000..6d000666c7e85dfc6fd6ae523ef24752a3b4fff9 --- /dev/null +++ b/src/vs/workbench/services/themes/electron-browser/editorStyles.ts @@ -0,0 +1,158 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import {IThemeDocument, IThemeSetting, IThemeSettingStyle} from 'vs/workbench/services/themes/common/themeService'; +import {Color} from 'vs/workbench/services/themes/common/color'; +import {getBaseThemeId, getSyntaxThemeId} from 'vs/platform/theme/common/themes'; + +export class TokenStylesContribution { + + public contributeStyles(themeId: string, themeDocument: IThemeDocument): string[] { + let cssRules = []; + let editorStyles = new EditorStyles(themeId, themeDocument); + themeDocument.settings.forEach((s: IThemeSetting, index, arr) => { + let scope: string | string[] = s.scope; + let settings = s.settings; + if (scope && settings) { + let rules = Array.isArray(scope) ? scope : scope.split(','); + let statements = this._settingsToStatements(settings); + rules.forEach(rule => { + rule = rule.trim().replace(/ /g, '.'); // until we have scope hierarchy in the editor dom: replace spaces with . + + cssRules.push(`.monaco-editor.${editorStyles.themeSelector} .token.${rule} { ${statements} }`); + }); + } + }); + return cssRules; + } + + private _settingsToStatements(settings: IThemeSettingStyle): string { + let statements: string[] = []; + + for (let settingName in settings) { + const value = settings[settingName]; + switch (settingName) { + case 'foreground': + let foreground = new Color(value); + statements.push(`color: ${foreground};`); + break; + case 'background': + // do not support background color for now, see bug 18924 + //let background = new Color(value); + //statements.push(`background-color: ${background};`); + break; + case 'fontStyle': + let segments = value.split(' '); + segments.forEach(s => { + switch (s) { + case 'italic': + statements.push(`font-style: italic;`); + break; + case 'bold': + statements.push(`font-weight: bold;`); + break; + case 'underline': + statements.push(`text-decoration: underline;`); + break; + } + }); + } + } + return statements.join(' '); + } +} + +export class EditorStylesContribution { + + public contributeStyles(themeId: string, themeDocument: IThemeDocument): string[] { + let cssRules = []; + let editorStyles = new EditorStyles(themeId, themeDocument); + if (editorStyles.editorStyleSettings) { + let themeSelector = editorStyles.themeSelector; + if (editorStyles.editorStyleSettings.background) { + let background = new Color(editorStyles.editorStyleSettings.background); + cssRules.push(`.monaco-editor.${themeSelector} .monaco-editor-background { background-color: ${background}; }`); + cssRules.push(`.monaco-editor.${themeSelector} .glyph-margin { background-color: ${background}; }`); + cssRules.push(`.${themeSelector} .monaco-workbench .monaco-editor-background { background-color: ${background}; }`); + } + if (editorStyles.editorStyleSettings.foreground) { + let foreground = new Color(editorStyles.editorStyleSettings.foreground); + cssRules.push(`.monaco-editor.${themeSelector} .token { color: ${foreground}; }`); + } + if (editorStyles.editorStyleSettings.selection) { + let selection = new Color(editorStyles.editorStyleSettings.selection); + cssRules.push(`.monaco-editor.${themeSelector} .focused .selected-text { background-color: ${selection}; }`); + cssRules.push(`.monaco-editor.${themeSelector} .selected-text { background-color: ${selection.transparent(0.5)}; }`); + } + if (editorStyles.editorStyleSettings.selectionHighlight) { + let selection = new Color(editorStyles.editorStyleSettings.selectionHighlight); + cssRules.push(`.monaco-editor.${themeSelector} .selectionHighlight { background-color: ${selection}; }`); + } + if (editorStyles.editorStyleSettings.wordHighlight) { + let selection = new Color(editorStyles.editorStyleSettings.wordHighlight); + cssRules.push(`.monaco-editor.${themeSelector} .wordHighlight { background-color: ${selection}; }`); + } + if (editorStyles.editorStyleSettings.wordHighlightStrong) { + let selection = new Color(editorStyles.editorStyleSettings.wordHighlightStrong); + cssRules.push(`.monaco-editor.${themeSelector} .wordHighlightStrong { background-color: ${selection}; }`); + } + if (editorStyles.editorStyleSettings.findLineHighlight) { + let selection = new Color(editorStyles.editorStyleSettings.findLineHighlight); + cssRules.push(`.monaco-editor.${themeSelector} .findLineHighlight { background-color: ${selection}; }`); + } + if (editorStyles.editorStyleSettings.lineHighlight) { + let lineHighlight = new Color(editorStyles.editorStyleSettings.lineHighlight); + cssRules.push(`.monaco-editor.${themeSelector} .current-line { background-color: ${lineHighlight}; border:0; }`); + } + if (editorStyles.editorStyleSettings.caret) { + let caret = new Color(editorStyles.editorStyleSettings.caret); + let oppositeCaret = caret.opposite(); + cssRules.push(`.monaco-editor.${themeSelector} .cursor { background-color: ${caret}; border-color: ${caret}; color: ${oppositeCaret}; }`); + } + if (editorStyles.editorStyleSettings.invisibles) { + let invisibles = new Color(editorStyles.editorStyleSettings.invisibles); + cssRules.push(`.monaco-editor.${themeSelector} .token.whitespace { color: ${invisibles} !important; }`); + } + if (editorStyles.editorStyleSettings.guide) { + let guide = new Color(editorStyles.editorStyleSettings.guide); + cssRules.push(`.monaco-editor.${themeSelector} .lines-content .cigr { background: ${guide}; }`); + } else if (editorStyles.editorStyleSettings.invisibles) { + let invisibles = new Color(editorStyles.editorStyleSettings.invisibles); + cssRules.push(`.monaco-editor.${themeSelector} .lines-content .cigr { background: ${invisibles}; }`); + } + } + return cssRules; + } +} + +interface EditorStyleSettings { + background?: string; + foreground?: string; + fontStyle?: string; + caret?: string; + invisibles?: string; + guide?: string; + lineHighlight?: string; + selection?: string; + selectionHighlight?: string; + findLineHighlight?: string; + wordHighlight?: string; + wordHighlightStrong?: string; +} + + +class EditorStyles { + + public themeSelector: string; + public editorStyleSettings: EditorStyleSettings = null; + + constructor(themeId: string, themeDocument: IThemeDocument) { + this.themeSelector = `${getBaseThemeId(themeId)}.${getSyntaxThemeId(themeId)}`; + let settings = themeDocument.settings[0]; + if (!settings.scope) { + this.editorStyleSettings = settings.settings; + } + } +} \ No newline at end of file diff --git a/src/vs/workbench/services/themes/electron-browser/themeService.ts b/src/vs/workbench/services/themes/electron-browser/themeService.ts index 37b4dd1ea02da26908bed3aaf21f04627d757976..f8ad5ea2e8e82e25b85fedf3370857b079ee2122 100644 --- a/src/vs/workbench/services/themes/electron-browser/themeService.ts +++ b/src/vs/workbench/services/themes/electron-browser/themeService.ts @@ -11,8 +11,9 @@ import Json = require('vs/base/common/json'); import {IThemeExtensionPoint} from 'vs/platform/theme/common/themeExtensionPoint'; import {IExtensionService} from 'vs/platform/extensions/common/extensions'; import {ExtensionsRegistry, IExtensionMessageCollector} from 'vs/platform/extensions/common/extensionsRegistry'; -import {IThemeService, IThemeData} from 'vs/workbench/services/themes/common/themeService'; -import {getBaseThemeId, getSyntaxThemeId} from 'vs/platform/theme/common/themes'; +import {IThemeService, IThemeData, IThemeSetting, IThemeDocument} from 'vs/workbench/services/themes/common/themeService'; +import {TokenStylesContribution, EditorStylesContribution} from 'vs/workbench/services/themes/electron-browser/editorStyles'; +import {getBaseThemeId} from 'vs/platform/theme/common/themes'; import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService'; import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; @@ -100,29 +101,6 @@ let iconThemeExtPoint = ExtensionsRegistry.registerExtensionPoint { return pfs.readFile(fileSetPath).then(content => { let errors: Json.ParseError[] = []; - let contentValue = Json.parse(content.toString(), errors); + let contentValue = Json.parse(content.toString(), errors); if (errors.length > 0) { return TPromise.wrapError(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing file icons file: {0}", errors.map(e => Json.getParseErrorMessage(e.error)).join(', ')))); } @@ -621,11 +599,11 @@ function applyTheme(theme: IInternalThemeData, onApply: (theme:IInternalThemeDat }); } -function _loadThemeDocument(themePath: string) : TPromise { +function _loadThemeDocument(themePath: string) : TPromise { return pfs.readFile(themePath).then(content => { if (Paths.extname(themePath) === '.json') { let errors: Json.ParseError[] = []; - let contentValue = Json.parse(content.toString(), errors); + let contentValue = Json.parse(content.toString(), errors); if (errors.length > 0) { return TPromise.wrapError(new Error(nls.localize('error.cannotparsejson', "Problems parsing JSON theme file: {0}", errors.map(e => Json.getParseErrorMessage(e.error)).join(', ')))); } @@ -645,116 +623,18 @@ function _loadThemeDocument(themePath: string) : TPromise { }); } -function _processThemeObject(themeId: string, themeDocument: ThemeDocument): string { +function _processThemeObject(themeId: string, themeDocument: IThemeDocument): string { let cssRules: string[] = []; - - let themeSettings : ThemeSetting[] = themeDocument.settings; - let editorSettings : ThemeSettingStyle = { - background: void 0, - foreground: void 0, - caret: void 0, - invisibles: void 0, - guide: void 0, - lineHighlight: void 0, - selection: void 0 - }; - - let themeSelector = `${getBaseThemeId(themeId)}.${getSyntaxThemeId(themeId)}`; + let themeSettings : IThemeSetting[] = themeDocument.settings; if (Array.isArray(themeSettings)) { - themeSettings.forEach((s : ThemeSetting, index, arr) => { - if (index === 0 && !s.scope) { - editorSettings = s.settings; - } else { - let scope: string | string[] = s.scope; - let settings = s.settings; - if (scope && settings) { - let rules = Array.isArray(scope) ? scope : scope.split(','); - let statements = _settingsToStatements(settings); - rules.forEach(rule => { - rule = rule.trim().replace(/ /g, '.'); // until we have scope hierarchy in the editor dom: replace spaces with . - - cssRules.push(`.monaco-editor.${themeSelector} .token.${rule} { ${statements} }`); - }); - } - } - }); - } - - if (editorSettings.background) { - let background = new Color(editorSettings.background); - cssRules.push(`.monaco-editor.${themeSelector} .monaco-editor-background { background-color: ${background}; }`); - cssRules.push(`.monaco-editor.${themeSelector} .glyph-margin { background-color: ${background}; }`); - cssRules.push(`.${themeSelector} .monaco-workbench .monaco-editor-background { background-color: ${background}; }`); - } - if (editorSettings.foreground) { - let foreground = new Color(editorSettings.foreground); - cssRules.push(`.monaco-editor.${themeSelector} .token { color: ${foreground}; }`); - } - if (editorSettings.selection) { - let selection = new Color(editorSettings.selection); - cssRules.push(`.monaco-editor.${themeSelector} .focused .selected-text { background-color: ${selection}; }`); - cssRules.push(`.monaco-editor.${themeSelector} .selected-text { background-color: ${selection.transparent(0.5)}; }`); - } - if (editorSettings.lineHighlight) { - let lineHighlight = new Color(editorSettings.lineHighlight); - cssRules.push(`.monaco-editor.${themeSelector} .current-line { background-color: ${lineHighlight}; border:0; }`); - } - if (editorSettings.caret) { - let caret = new Color(editorSettings.caret); - let oppositeCaret = caret.opposite(); - cssRules.push(`.monaco-editor.${themeSelector} .cursor { background-color: ${caret}; border-color: ${caret}; color: ${oppositeCaret}; }`); - } - if (editorSettings.invisibles) { - let invisibles = new Color(editorSettings.invisibles); - cssRules.push(`.monaco-editor.${themeSelector} .token.whitespace { color: ${invisibles} !important; }`); - } - if (editorSettings.guide) { - let guide = new Color(editorSettings.guide); - cssRules.push(`.monaco-editor.${themeSelector} .lines-content .cigr { background: ${guide}; }`); - } else if (editorSettings.invisibles) { - let invisibles = new Color(editorSettings.invisibles); - cssRules.push(`.monaco-editor.${themeSelector} .lines-content .cigr { background: ${invisibles}; }`); + cssRules= cssRules.concat(new TokenStylesContribution().contributeStyles(themeId, themeDocument)); + cssRules= cssRules.concat(new EditorStylesContribution().contributeStyles(themeId, themeDocument)); } return cssRules.join('\n'); } -function _settingsToStatements(settings: ThemeSettingStyle): string { - let statements: string[] = []; - - for (let settingName in settings) { - const value = settings[settingName]; - switch (settingName) { - case 'foreground': - let foreground = new Color(value); - statements.push(`color: ${foreground};`); - break; - case 'background': - // do not support background color for now, see bug 18924 - //let background = new Color(value); - //statements.push(`background-color: ${background};`); - break; - case 'fontStyle': - let segments = value.split(' '); - segments.forEach(s => { - switch (s) { - case 'italic': - statements.push(`font-style: italic;`); - break; - case 'bold': - statements.push(`font-weight: bold;`); - break; - case 'underline': - statements.push(`text-decoration: underline;`); - break; - } - }); - } - } - return statements.join(' '); -} - let colorThemeRulesClassName = 'contributedColorTheme'; let iconThemeRulesClassName = 'contributedIconTheme'; @@ -771,60 +651,6 @@ function _applyRules(styleSheetContent: string, rulesClassName: string) { } } -interface RGBA { r: number; g: number; b: number; a: number; } - -class Color { - - private parsed: RGBA; - private str: string; - - constructor(arg: string | RGBA) { - if (typeof arg === 'string') { - this.parsed = Color.parse(arg); - } else { - this.parsed = arg; - } - this.str = null; - } - - private static parse(color: string): RGBA { - function parseHex(str: string) { - return parseInt('0x' + str); - } - - if (color.charAt(0) === '#' && color.length >= 7) { - let r = parseHex(color.substr(1, 2)); - let g = parseHex(color.substr(3, 2)); - let b = parseHex(color.substr(5, 2)); - let a = color.length === 9 ? parseHex(color.substr(7, 2)) / 0xff : 1; - return { r, g, b, a }; - } - return { r: 255, g: 0, b: 0, a: 1 }; - } - - public toString(): string { - if (!this.str) { - let p = this.parsed; - this.str = `rgba(${p.r}, ${p.g}, ${p.b}, ${+p.a.toFixed(2)})`; - } - return this.str; - } - - public transparent(factor: number): Color { - let p = this.parsed; - return new Color({ r: p.r, g: p.g, b: p.b, a: p.a * factor }); - } - - public opposite(): Color { - return new Color({ - r: 255 - this.parsed.r, - g: 255 - this.parsed.g, - b: 255 - this.parsed.b, - a : this.parsed.a - }); - } -} - const schemaId = 'vscode://schemas/icon-theme'; const schema: IJSONSchema = { type: 'object',