Skip to content

Commit 9098d9f

Browse files
committed
[themes] let extensions contribute colors. Fixes microsoft#14309
1 parent 698382a commit 9098d9f

3 files changed

Lines changed: 143 additions & 1 deletion

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
'use strict';
6+
7+
import nls = require('vs/nls');
8+
import { ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry';
9+
import { registerColor, getColorRegistry } from 'vs/platform/theme/common/colorRegistry';
10+
import { Color } from 'vs/base/common/color';
11+
12+
interface IColorExtensionPoint {
13+
id: string;
14+
description: string;
15+
defaults: { light: string, dark: string, highContrast: string };
16+
}
17+
18+
const colorReferenceSchema = getColorRegistry().getColorReferenceSchema();
19+
const colorIdPattern = '^\\w+[.\\w+]*$';
20+
21+
const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IColorExtensionPoint[]>('colors', [], {
22+
description: nls.localize('contributes.color', 'Contributes extension defined themable colors'),
23+
type: 'array',
24+
items: {
25+
type: 'object',
26+
properties: {
27+
id: {
28+
type: 'string',
29+
description: nls.localize('contributes.color.id', 'The identifier of the themable color'),
30+
pattern: colorIdPattern,
31+
patternErrorMessage: nls.localize('contributes.color.id.format', 'Identifiers should be in the form aa[.bb]*'),
32+
},
33+
description: {
34+
type: 'string',
35+
description: nls.localize('contributes.color.description', 'The description of the themable color'),
36+
},
37+
defaults: {
38+
type: 'object',
39+
properties: {
40+
light: {
41+
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.'),
42+
type: 'string',
43+
anyOf: [
44+
colorReferenceSchema,
45+
{ type: 'string', format: 'color' }
46+
]
47+
},
48+
dark: {
49+
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.'),
50+
type: 'string',
51+
anyOf: [
52+
colorReferenceSchema,
53+
{ type: 'string', format: 'color' }
54+
]
55+
},
56+
highContrast: {
57+
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.'),
58+
type: 'string',
59+
anyOf: [
60+
colorReferenceSchema,
61+
{ type: 'string', format: 'color' }
62+
]
63+
}
64+
}
65+
},
66+
}
67+
}
68+
});
69+
70+
export class ColorExtensionPoint {
71+
72+
constructor() {
73+
configurationExtPoint.setHandler((extensions) => {
74+
for (var i = 0; i < extensions.length; i++) {
75+
var extensionValue = <IColorExtensionPoint[]>extensions[i].value;
76+
var collector = extensions[i].collector;
77+
78+
if (!extensionValue || !Array.isArray(extensionValue)) {
79+
collector.error(nls.localize('invalid.colorConfiguration', "'configuration.colors' must be a array"));
80+
return;
81+
}
82+
let parseColorValue = (s: string, name: string) => {
83+
if (s.length > 0) {
84+
if (s[0] === '#') {
85+
return Color.Format.CSS.parseHex(s);
86+
} else {
87+
return s;
88+
}
89+
}
90+
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));
91+
return Color.red;
92+
};
93+
94+
extensionValue.forEach(extension => {
95+
if (typeof extension.id !== 'string' || extension.id.length === 0) {
96+
collector.error(nls.localize('invalid.id', "'configuration.colors.id' must be defined an can not be empty"));
97+
return;
98+
}
99+
if (!extension.id.match(colorIdPattern)) {
100+
collector.error(nls.localize('invalid.id.format', "'configuration.colors.id' must follow the word[.word]*"));
101+
return;
102+
}
103+
if (typeof extension.description !== 'string' || extension.id.length === 0) {
104+
collector.error(nls.localize('invalid.description', "'configuration.colors.description' must be defined an can not be empty"));
105+
return;
106+
}
107+
let defaults = extension.defaults;
108+
if (typeof defaults !== 'object' || typeof defaults.light !== 'string' || typeof defaults.dark !== 'string' || typeof defaults.highContrast !== 'string') {
109+
collector.error(nls.localize('invalid.defaults', "'configuration.colors.defaults' must be defined and must contain 'light', 'dark' and 'highContrast'"));
110+
return;
111+
}
112+
registerColor(extension.id, {
113+
light: parseColorValue(defaults.light, 'configuration.colors.defaults.light'),
114+
dark: parseColorValue(defaults.dark, 'configuration.colors.defaults.dark'),
115+
hc: parseColorValue(defaults.highContrast, 'configuration.colors.defaults.highContrast')
116+
}, extension.description);
117+
});
118+
}
119+
});
120+
}
121+
}
122+
123+
124+

src/vs/platform/theme/common/colorRegistry.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,15 @@ export interface IColorRegistry {
6363
resolveDefaultColor(id: ColorIdentifier, theme: ITheme): Color;
6464

6565
/**
66-
* JSON schema of all colors
66+
* JSON schema for an object to assign color values to one of the color contrbutions.
6767
*/
6868
getColorSchema(): IJSONSchema;
6969

70+
/**
71+
* JSON schema to for a reference to a color contrbution.
72+
*/
73+
getColorReferenceSchema(): IJSONSchema;
74+
7075
}
7176

7277
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
7580
class ColorRegistry implements IColorRegistry {
7681
private colorsById: { [key: string]: ColorContribution };
7782
private colorSchema: IJSONSchema = { type: 'object', description: nls.localize('schema.colors', "Colors used in the workbench."), properties: {}, additionalProperties: false };
83+
private colorReferenceSchema: IJSONSchema = { type: 'string', enum: [], enumDescriptions: [] };
7884

7985
constructor() {
8086
this.colorsById = {};
@@ -84,6 +90,8 @@ class ColorRegistry implements IColorRegistry {
8490
let colorContribution = { id, description, defaults };
8591
this.colorsById[id] = colorContribution;
8692
this.colorSchema.properties[id] = { type: 'string', description, format: 'color', pattern: colorPattern, patternErrorMessage: colorPatternErrorMessage };
93+
this.colorReferenceSchema.enum.push(id);
94+
this.colorReferenceSchema.enumDescriptions.push(description);
8795
return id;
8896
}
8997

@@ -104,6 +112,10 @@ class ColorRegistry implements IColorRegistry {
104112
return this.colorSchema;
105113
}
106114

115+
public getColorReferenceSchema(): IJSONSchema {
116+
return this.colorReferenceSchema;
117+
}
118+
107119
public toString() {
108120
let sorter = (a: string, b: string) => {
109121
let cat1 = a.indexOf('.') === -1 ? 0 : 1;
@@ -126,6 +138,10 @@ export function registerColor(id: string, defaults: ColorDefaults, description:
126138
return colorRegistry.registerColor(id, defaults, description);
127139
}
128140

141+
export function getColorRegistry(): IColorRegistry {
142+
return colorRegistry;
143+
}
144+
129145
// ----- base colors
130146

131147
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."));

src/vs/workbench/api/electron-browser/extensionHost.contribution.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { MainThreadCredentials } from './mainThreadCredentials';
4141
// --- other interested parties
4242
import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors';
4343
import { JSONValidationExtensionPoint } from 'vs/platform/jsonschemas/common/jsonValidationExtensionPoint';
44+
import { ColorExtensionPoint } from 'vs/platform/theme/common/colorExtensionPoint';
4445
import { LanguageConfigurationFileHandler } from 'vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint';
4546
import { SaveParticipant } from './mainThreadSaveParticipant';
4647

@@ -99,6 +100,7 @@ export class ExtHostContribution implements IWorkbenchContribution {
99100

100101
// Other interested parties
101102
create(JSONValidationExtensionPoint);
103+
create(ColorExtensionPoint);
102104
this.instantiationService.createInstance(LanguageConfigurationFileHandler);
103105
create(MainThreadFileSystemEventService);
104106
create(SaveParticipant);

0 commit comments

Comments
 (0)