diff --git a/src/vs/platform/theme/common/colorExtensionPoint.ts b/src/vs/platform/theme/common/colorExtensionPoint.ts new file mode 100644 index 00000000000..69fa39e80a7 --- /dev/null +++ b/src/vs/platform/theme/common/colorExtensionPoint.ts @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import nls = require('vs/nls'); +import { ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry'; +import { registerColor, getColorRegistry } from 'vs/platform/theme/common/colorRegistry'; +import { Color } from 'vs/base/common/color'; + +interface IColorExtensionPoint { + id: string; + description: string; + defaults: { light: string, dark: string, highContrast: string }; +} + +const colorReferenceSchema = getColorRegistry().getColorReferenceSchema(); +const colorIdPattern = '^\\w+[.\\w+]*$'; + +const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint('colors', [], { + description: nls.localize('contributes.color', 'Contributes extension defined themable colors'), + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + description: nls.localize('contributes.color.id', 'The identifier of the themable color'), + pattern: colorIdPattern, + patternErrorMessage: nls.localize('contributes.color.id.format', 'Identifiers should be in the form aa[.bb]*'), + }, + description: { + type: 'string', + description: nls.localize('contributes.color.description', 'The description of the themable color'), + }, + defaults: { + type: 'object', + properties: { + light: { + description: nls.localize('contributes.defaults.light', 'The default color for light themes. Either a color value in hex (#RRGGBB[AA]) or the identifier of a themable color which provides the default.'), + type: 'string', + anyOf: [ + colorReferenceSchema, + { type: 'string', format: 'color' } + ] + }, + dark: { + description: nls.localize('contributes.defaults.dark', 'The default color for dark themes. Either a color value in hex (#RRGGBB[AA]) or the identifier of a themable color which provides the default.'), + type: 'string', + anyOf: [ + colorReferenceSchema, + { type: 'string', format: 'color' } + ] + }, + highContrast: { + description: nls.localize('contributes.defaults.highContrast', 'The default color for high contrast themes. Either a color value in hex (#RRGGBB[AA]) or the identifier of a themable color which provides the default.'), + type: 'string', + anyOf: [ + colorReferenceSchema, + { type: 'string', format: 'color' } + ] + } + } + }, + } + } +}); + +export class ColorExtensionPoint { + + constructor() { + configurationExtPoint.setHandler((extensions) => { + for (var i = 0; i < extensions.length; i++) { + var extensionValue = extensions[i].value; + var collector = extensions[i].collector; + + if (!extensionValue || !Array.isArray(extensionValue)) { + collector.error(nls.localize('invalid.colorConfiguration', "'configuration.colors' must be a array")); + return; + } + let parseColorValue = (s: string, name: string) => { + if (s.length > 0) { + if (s[0] === '#') { + return Color.Format.CSS.parseHex(s); + } else { + return s; + } + } + collector.error(nls.localize('invalid.default.colorType', "{0} must be either a color value in hex (#RRGGBB[AA] or #RGB[A]) or the identifier of a themable color which provides the default.", name)); + return Color.red; + }; + + extensionValue.forEach(extension => { + if (typeof extension.id !== 'string' || extension.id.length === 0) { + collector.error(nls.localize('invalid.id', "'configuration.colors.id' must be defined an can not be empty")); + return; + } + if (!extension.id.match(colorIdPattern)) { + collector.error(nls.localize('invalid.id.format', "'configuration.colors.id' must follow the word[.word]*")); + return; + } + if (typeof extension.description !== 'string' || extension.id.length === 0) { + collector.error(nls.localize('invalid.description', "'configuration.colors.description' must be defined an can not be empty")); + return; + } + let defaults = extension.defaults; + if (typeof defaults !== 'object' || typeof defaults.light !== 'string' || typeof defaults.dark !== 'string' || typeof defaults.highContrast !== 'string') { + collector.error(nls.localize('invalid.defaults', "'configuration.colors.defaults' must be defined and must contain 'light', 'dark' and 'highContrast'")); + return; + } + registerColor(extension.id, { + light: parseColorValue(defaults.light, 'configuration.colors.defaults.light'), + dark: parseColorValue(defaults.dark, 'configuration.colors.defaults.dark'), + hc: parseColorValue(defaults.highContrast, 'configuration.colors.defaults.highContrast') + }, extension.description); + }); + } + }); + } +} + + + diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 8976e487b60..cd8ce27509c 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -63,10 +63,15 @@ export interface IColorRegistry { resolveDefaultColor(id: ColorIdentifier, theme: ITheme): Color; /** - * JSON schema of all colors + * JSON schema for an object to assign color values to one of the color contrbutions. */ getColorSchema(): IJSONSchema; + /** + * JSON schema to for a reference to a color contrbution. + */ + getColorReferenceSchema(): IJSONSchema; + } const colorPattern = '^#([0-9A-Fa-f]{3,4}|([0-9A-Fa-f]{2}){3,4})$'; @@ -75,6 +80,7 @@ const colorPatternErrorMessage = nls.localize('invalid.color', 'Invalid color fo class ColorRegistry implements IColorRegistry { private colorsById: { [key: string]: ColorContribution }; private colorSchema: IJSONSchema = { type: 'object', description: nls.localize('schema.colors', "Colors used in the workbench."), properties: {}, additionalProperties: false }; + private colorReferenceSchema: IJSONSchema = { type: 'string', enum: [], enumDescriptions: [] }; constructor() { this.colorsById = {}; @@ -84,6 +90,8 @@ class ColorRegistry implements IColorRegistry { let colorContribution = { id, description, defaults }; this.colorsById[id] = colorContribution; this.colorSchema.properties[id] = { type: 'string', description, format: 'color', pattern: colorPattern, patternErrorMessage: colorPatternErrorMessage }; + this.colorReferenceSchema.enum.push(id); + this.colorReferenceSchema.enumDescriptions.push(description); return id; } @@ -104,6 +112,10 @@ class ColorRegistry implements IColorRegistry { return this.colorSchema; } + public getColorReferenceSchema(): IJSONSchema { + return this.colorReferenceSchema; + } + public toString() { let sorter = (a: string, b: string) => { let cat1 = a.indexOf('.') === -1 ? 0 : 1; @@ -126,6 +138,10 @@ export function registerColor(id: string, defaults: ColorDefaults, description: return colorRegistry.registerColor(id, defaults, description); } +export function getColorRegistry(): IColorRegistry { + return colorRegistry; +} + // ----- base colors export const foreground = registerColor('foreground', { dark: '#CCCCCC', light: '#6C6C6C', hc: '#FFFFFF' }, nls.localize('foreground', "Overall foreground color. This color is only used if not overridden by a component.")); diff --git a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts index d13fa311d98..758dcb50756 100644 --- a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts @@ -41,6 +41,7 @@ import { MainThreadCredentials } from './mainThreadCredentials'; // --- other interested parties import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors'; import { JSONValidationExtensionPoint } from 'vs/platform/jsonschemas/common/jsonValidationExtensionPoint'; +import { ColorExtensionPoint } from 'vs/platform/theme/common/colorExtensionPoint'; import { LanguageConfigurationFileHandler } from 'vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint'; import { SaveParticipant } from './mainThreadSaveParticipant'; @@ -99,6 +100,7 @@ export class ExtHostContribution implements IWorkbenchContribution { // Other interested parties create(JSONValidationExtensionPoint); + create(ColorExtensionPoint); this.instantiationService.createInstance(LanguageConfigurationFileHandler); create(MainThreadFileSystemEventService); create(SaveParticipant);