Merge pull request #84497 from microsoft/aeschli/tokenScopes
First cut theming support for semantic colors
This commit is contained in:
commit
587e25ca53
|
@ -252,6 +252,9 @@
|
|||
],
|
||||
"colors": {
|
||||
|
||||
"editor.background": "#000c18",
|
||||
"editor.foreground": "#6688cc",
|
||||
|
||||
// Base
|
||||
// "foreground": "",
|
||||
"focusBorder": "#596F99",
|
||||
|
@ -295,8 +298,6 @@
|
|||
"scrollbarSlider.hoverBackground": "#3B3F5188",
|
||||
|
||||
// Editor
|
||||
"editor.background": "#000c18",
|
||||
// "editor.foreground": "#6688cc",
|
||||
"editorWidget.background": "#262641",
|
||||
"editorCursor.foreground": "#ddbb88",
|
||||
"editorWhitespace.foreground": "#103050",
|
||||
|
|
|
@ -14,6 +14,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
|
|||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Extensions as ThemingExtensions, ICssStyleCollector, IIconTheme, IThemingRegistry } from 'vs/platform/theme/common/themeService';
|
||||
import { TokenStyle, TokenClassification, ProbeScope } from 'vs/platform/theme/common/tokenClassificationRegistry';
|
||||
|
||||
const VS_THEME_NAME = 'vs';
|
||||
const VS_DARK_THEME_NAME = 'vs-dark';
|
||||
|
@ -23,6 +24,7 @@ const colorRegistry = Registry.as<IColorRegistry>(Extensions.ColorContribution);
|
|||
const themingRegistry = Registry.as<IThemingRegistry>(ThemingExtensions.ThemingContribution);
|
||||
|
||||
class StandaloneTheme implements IStandaloneTheme {
|
||||
|
||||
public readonly id: string;
|
||||
public readonly themeName: string;
|
||||
|
||||
|
@ -128,6 +130,14 @@ class StandaloneTheme implements IStandaloneTheme {
|
|||
}
|
||||
return this._tokenTheme;
|
||||
}
|
||||
|
||||
getTokenStyle(classification: TokenClassification, useDefault?: boolean | undefined): TokenStyle | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function isBuiltinTheme(themeName: string): themeName is BuiltinTheme {
|
||||
|
|
|
@ -54,7 +54,11 @@ suite('TokenizationSupport2Adapter', () => {
|
|||
|
||||
defines: (color: ColorIdentifier): boolean => {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
},
|
||||
|
||||
getTokenStyle: () => undefined,
|
||||
resolveScopes: () => undefined
|
||||
|
||||
};
|
||||
}
|
||||
public getIconTheme(): IIconTheme {
|
||||
|
|
|
@ -103,7 +103,7 @@ class ColorRegistry implements IColorRegistry {
|
|||
public registerColor(id: string, defaults: ColorDefaults | null, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier {
|
||||
let colorContribution: ColorContribution = { id, description, defaults, needsTransparency, deprecationMessage };
|
||||
this.colorsById[id] = colorContribution;
|
||||
let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '#ff0000' }] };
|
||||
let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] };
|
||||
if (deprecationMessage) {
|
||||
propertySchema.deprecationMessage = deprecationMessage;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import * as platform from 'vs/platform/registry/common/platform';
|
|||
import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { TokenStyle, TokenClassification, ProbeScope } from 'vs/platform/theme/common/tokenClassificationRegistry';
|
||||
|
||||
export const IThemeService = createDecorator<IThemeService>('themeService');
|
||||
|
||||
|
@ -59,6 +60,10 @@ export interface ITheme {
|
|||
* default color will be used.
|
||||
*/
|
||||
defines(color: ColorIdentifier): boolean;
|
||||
|
||||
getTokenStyle(classification: TokenClassification, useDefault?: boolean): TokenStyle | undefined;
|
||||
|
||||
resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined;
|
||||
}
|
||||
|
||||
export interface IIconTheme {
|
||||
|
|
478
src/vs/platform/theme/common/tokenClassificationRegistry.ts
Normal file
478
src/vs/platform/theme/common/tokenClassificationRegistry.ts
Normal file
|
@ -0,0 +1,478 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as platform from 'vs/platform/registry/common/platform';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
|
||||
|
||||
// ------ API types
|
||||
|
||||
export const TOKEN_TYPE_WILDCARD = '*';
|
||||
export const TOKEN_TYPE_WILDCARD_NUM = -1;
|
||||
|
||||
// qualified string [type|*](.modifier)*
|
||||
export type TokenClassificationString = string;
|
||||
|
||||
export interface TokenClassification {
|
||||
type: number;
|
||||
modifiers: number;
|
||||
}
|
||||
|
||||
export interface TokenTypeOrModifierContribution {
|
||||
readonly num: number;
|
||||
readonly id: string;
|
||||
readonly description: string;
|
||||
readonly deprecationMessage: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
export interface TokenStyleData {
|
||||
foreground?: Color;
|
||||
bold?: boolean;
|
||||
underline?: boolean;
|
||||
italic?: boolean;
|
||||
}
|
||||
|
||||
export class TokenStyle implements Readonly<TokenStyleData> {
|
||||
constructor(
|
||||
public readonly foreground?: Color,
|
||||
public readonly bold?: boolean,
|
||||
public readonly underline?: boolean,
|
||||
public readonly italic?: boolean,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace TokenStyle {
|
||||
export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }) {
|
||||
return new TokenStyle(data.foreground, data.bold, data.underline, data.italic);
|
||||
}
|
||||
}
|
||||
|
||||
export type ProbeScope = string[];
|
||||
|
||||
export interface TokenStyleFunction {
|
||||
(theme: ITheme): TokenStyle | undefined;
|
||||
}
|
||||
|
||||
export interface TokenStyleDefaults {
|
||||
scopesToProbe: ProbeScope[];
|
||||
light: TokenStyleValue | null;
|
||||
dark: TokenStyleValue | null;
|
||||
hc: TokenStyleValue | null;
|
||||
}
|
||||
|
||||
export interface TokenStylingDefaultRule {
|
||||
classification: TokenClassification;
|
||||
matchScore: number;
|
||||
defaults: TokenStyleDefaults;
|
||||
}
|
||||
|
||||
export interface TokenStylingRule {
|
||||
classification: TokenClassification;
|
||||
matchScore: number;
|
||||
value: TokenStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* A TokenStyle Value is either a token style literal, or a TokenClassificationString
|
||||
*/
|
||||
export type TokenStyleValue = TokenStyle | TokenClassificationString;
|
||||
|
||||
// TokenStyle registry
|
||||
export const Extensions = {
|
||||
TokenClassificationContribution: 'base.contributions.tokenClassification'
|
||||
};
|
||||
|
||||
export interface ITokenClassificationRegistry {
|
||||
|
||||
readonly onDidChangeSchema: Event<void>;
|
||||
|
||||
/**
|
||||
* Register a token type to the registry.
|
||||
* @param id The TokenType id as used in theme description files
|
||||
* @description the description
|
||||
*/
|
||||
registerTokenType(id: string, description: string): void;
|
||||
|
||||
/**
|
||||
* Register a token modifier to the registry.
|
||||
* @param id The TokenModifier id as used in theme description files
|
||||
* @description the description
|
||||
*/
|
||||
registerTokenModifier(id: string, description: string): void;
|
||||
|
||||
getTokenClassificationFromString(str: TokenClassificationString): TokenClassification | undefined;
|
||||
getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined;
|
||||
|
||||
getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule;
|
||||
|
||||
/**
|
||||
* Register a TokenStyle default to the registry.
|
||||
* @param selector The rule selector
|
||||
* @param defaults The default values
|
||||
*/
|
||||
registerTokenStyleDefault(selector: TokenClassification, defaults: TokenStyleDefaults): void;
|
||||
|
||||
/**
|
||||
* Deregister a TokenType from the registry.
|
||||
*/
|
||||
deregisterTokenType(id: string): void;
|
||||
|
||||
/**
|
||||
* Deregister a TokenModifier from the registry.
|
||||
*/
|
||||
deregisterTokenModifier(id: string): void;
|
||||
|
||||
/**
|
||||
* Get all TokenType contributions
|
||||
*/
|
||||
getTokenTypes(): TokenTypeOrModifierContribution[];
|
||||
|
||||
/**
|
||||
* Get all TokenModifier contributions
|
||||
*/
|
||||
getTokenModifiers(): TokenTypeOrModifierContribution[];
|
||||
|
||||
/**
|
||||
* Resolves a token classification against the given rules and default rules from the registry.
|
||||
*/
|
||||
resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[] | undefined, customThemingRules: TokenStylingRule[], theme: ITheme): TokenStyle | undefined;
|
||||
|
||||
/**
|
||||
* JSON schema for an object to assign styling to token classifications
|
||||
*/
|
||||
getTokenStylingSchema(): IJSONSchema;
|
||||
}
|
||||
|
||||
class TokenClassificationRegistry implements ITokenClassificationRegistry {
|
||||
|
||||
private readonly _onDidChangeSchema = new Emitter<void>();
|
||||
readonly onDidChangeSchema: Event<void> = this._onDidChangeSchema.event;
|
||||
|
||||
private currentTypeNumber = 0;
|
||||
private currentModifierBit = 1;
|
||||
|
||||
private tokenTypeById: { [key: string]: TokenTypeOrModifierContribution };
|
||||
private tokenModifierById: { [key: string]: TokenTypeOrModifierContribution };
|
||||
|
||||
private tokenStylingDefaultRules: TokenStylingDefaultRule[] = [];
|
||||
|
||||
private tokenStylingSchema: IJSONSchema & { properties: IJSONSchemaMap } = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
definitions: {
|
||||
style: {
|
||||
type: 'object',
|
||||
description: nls.localize('schema.token.settings', 'Colors and styles for the token.'),
|
||||
properties: {
|
||||
foreground: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.token.foreground', 'Foreground color for the token.'),
|
||||
format: 'color-hex',
|
||||
default: '#ff0000'
|
||||
},
|
||||
background: {
|
||||
type: 'string',
|
||||
deprecationMessage: nls.localize('schema.token.background.warning', 'Token background colors are currently not supported.')
|
||||
},
|
||||
fontStyle: {
|
||||
type: 'string',
|
||||
description: nls.localize('schema.token.fontStyle', 'Font style of the rule: \'italic\', \'bold\' or \'underline\', \'-italic\', \'-bold\' or \'-underline\'or a combination. The empty string unsets inherited settings.'),
|
||||
pattern: '^(\\s*(-?italic|-?bold|-?underline))*\\s*$',
|
||||
patternErrorMessage: nls.localize('schema.fontStyle.error', 'Font style must be \'italic\', \'bold\' or \'underline\' to set a style or \'-italic\', \'-bold\' or \'-underline\' to unset or a combination. The empty string unsets all styles.'),
|
||||
defaultSnippets: [{ label: nls.localize('schema.token.fontStyle.none', 'None (clear inherited style)'), bodyText: '""' }, { body: 'italic' }, { body: 'bold' }, { body: 'underline' }, { body: '-italic' }, { body: '-bold' }, { body: '-underline' }, { body: 'italic bold' }, { body: 'italic underline' }, { body: 'bold underline' }, { body: 'italic bold underline' }]
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
defaultSnippets: [{ body: { foreground: '${1:#FF0000}', fontStyle: '${2:bold}' } }]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.tokenTypeById = {};
|
||||
this.tokenModifierById = {};
|
||||
|
||||
this.tokenTypeById[TOKEN_TYPE_WILDCARD] = { num: TOKEN_TYPE_WILDCARD_NUM, id: TOKEN_TYPE_WILDCARD, description: '', deprecationMessage: undefined };
|
||||
}
|
||||
|
||||
public registerTokenType(id: string, description: string, deprecationMessage?: string): void {
|
||||
const num = this.currentTypeNumber++;
|
||||
let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage };
|
||||
this.tokenTypeById[id] = tokenStyleContribution;
|
||||
|
||||
this.tokenStylingSchema.properties[id] = getStylingSchemeEntry(description, deprecationMessage);
|
||||
}
|
||||
|
||||
public registerTokenModifier(id: string, description: string, deprecationMessage?: string): void {
|
||||
const num = this.currentModifierBit;
|
||||
this.currentModifierBit = this.currentModifierBit * 2;
|
||||
let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage };
|
||||
this.tokenModifierById[id] = tokenStyleContribution;
|
||||
|
||||
this.tokenStylingSchema.properties[`*.${id}`] = getStylingSchemeEntry(description, deprecationMessage);
|
||||
}
|
||||
|
||||
public getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined {
|
||||
const tokenTypeDesc = this.tokenTypeById[type];
|
||||
if (!tokenTypeDesc) {
|
||||
return undefined;
|
||||
}
|
||||
let allModifierBits = 0;
|
||||
for (const modifier of modifiers) {
|
||||
const tokenModifierDesc = this.tokenModifierById[modifier];
|
||||
if (tokenModifierDesc) {
|
||||
allModifierBits |= tokenModifierDesc.num;
|
||||
}
|
||||
}
|
||||
return { type: tokenTypeDesc.num, modifiers: allModifierBits };
|
||||
}
|
||||
|
||||
public getTokenClassificationFromString(str: TokenClassificationString): TokenClassification | undefined {
|
||||
const parts = str.split('.');
|
||||
const type = parts.shift();
|
||||
if (type) {
|
||||
return this.getTokenClassification(type, parts);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule {
|
||||
return { classification, matchScore: getTokenStylingScore(classification), value };
|
||||
}
|
||||
|
||||
public registerTokenStyleDefault(classification: TokenClassification, defaults: TokenStyleDefaults): void {
|
||||
this.tokenStylingDefaultRules.push({ classification, matchScore: getTokenStylingScore(classification), defaults });
|
||||
}
|
||||
|
||||
public deregisterTokenType(id: string): void {
|
||||
delete this.tokenTypeById[id];
|
||||
delete this.tokenStylingSchema.properties[id];
|
||||
}
|
||||
|
||||
public deregisterTokenModifier(id: string): void {
|
||||
delete this.tokenModifierById[id];
|
||||
delete this.tokenStylingSchema.properties[`*.${id}`];
|
||||
}
|
||||
|
||||
public getTokenTypes(): TokenTypeOrModifierContribution[] {
|
||||
return Object.keys(this.tokenTypeById).map(id => this.tokenTypeById[id]);
|
||||
}
|
||||
|
||||
public getTokenModifiers(): TokenTypeOrModifierContribution[] {
|
||||
return Object.keys(this.tokenModifierById).map(id => this.tokenModifierById[id]);
|
||||
}
|
||||
|
||||
public resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[] | undefined, customThemingRules: TokenStylingRule[], theme: ITheme): TokenStyle | undefined {
|
||||
let result: any = {
|
||||
foreground: undefined,
|
||||
bold: undefined,
|
||||
underline: undefined,
|
||||
italic: undefined
|
||||
};
|
||||
let score = {
|
||||
foreground: -1,
|
||||
bold: -1,
|
||||
underline: -1,
|
||||
italic: -1
|
||||
};
|
||||
|
||||
function _processStyle(matchScore: number, style: TokenStyle) {
|
||||
if (style.foreground && score.foreground <= matchScore) {
|
||||
score.foreground = matchScore;
|
||||
result.foreground = style.foreground;
|
||||
}
|
||||
for (let p of ['bold', 'underline', 'italic']) {
|
||||
const property = p as keyof TokenStyle;
|
||||
const info = style[property];
|
||||
if (info !== undefined) {
|
||||
if (score[property] <= matchScore) {
|
||||
score[property] = matchScore;
|
||||
result[property] = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (themingRules === undefined) {
|
||||
for (const rule of this.tokenStylingDefaultRules) {
|
||||
const matchScore = match(rule, classification);
|
||||
if (matchScore >= 0) {
|
||||
let style = theme.resolveScopes(rule.defaults.scopesToProbe);
|
||||
if (!style) {
|
||||
style = this.resolveTokenStyleValue(rule.defaults[theme.type], theme);
|
||||
}
|
||||
if (style) {
|
||||
_processStyle(matchScore, style);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const rule of themingRules) {
|
||||
const matchScore = match(rule, classification);
|
||||
if (matchScore >= 0) {
|
||||
_processStyle(matchScore, rule.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const rule of customThemingRules) {
|
||||
const matchScore = match(rule, classification);
|
||||
if (matchScore >= 0) {
|
||||
_processStyle(matchScore, rule.value);
|
||||
}
|
||||
}
|
||||
return TokenStyle.fromData(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tokenStyleValue Resolve a tokenStyleValue in the context of a theme
|
||||
*/
|
||||
private resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | null, theme: ITheme): TokenStyle | undefined {
|
||||
if (tokenStyleValue === null) {
|
||||
return undefined;
|
||||
} else if (typeof tokenStyleValue === 'string') {
|
||||
const classification = this.getTokenClassificationFromString(tokenStyleValue);
|
||||
if (classification) {
|
||||
return theme.getTokenStyle(classification);
|
||||
}
|
||||
} else if (typeof tokenStyleValue === 'object') {
|
||||
return tokenStyleValue;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getTokenStylingSchema(): IJSONSchema {
|
||||
return this.tokenStylingSchema;
|
||||
}
|
||||
|
||||
|
||||
public toString() {
|
||||
let sorter = (a: string, b: string) => {
|
||||
let cat1 = a.indexOf('.') === -1 ? 0 : 1;
|
||||
let cat2 = b.indexOf('.') === -1 ? 0 : 1;
|
||||
if (cat1 !== cat2) {
|
||||
return cat1 - cat2;
|
||||
}
|
||||
return a.localeCompare(b);
|
||||
};
|
||||
|
||||
return Object.keys(this.tokenTypeById).sort(sorter).map(k => `- \`${k}\`: ${this.tokenTypeById[k].description}`).join('\n');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function match(themeSelector: TokenStylingRule | TokenStylingDefaultRule, classification: TokenClassification): number {
|
||||
const selectorType = themeSelector.classification.type;
|
||||
if (selectorType !== TOKEN_TYPE_WILDCARD_NUM && selectorType !== classification.type) {
|
||||
return -1;
|
||||
}
|
||||
const selectorModifier = themeSelector.classification.modifiers;
|
||||
if ((classification.modifiers & selectorModifier) !== selectorModifier) {
|
||||
return -1;
|
||||
}
|
||||
return themeSelector.matchScore;
|
||||
}
|
||||
|
||||
|
||||
const tokenClassificationRegistry = new TokenClassificationRegistry();
|
||||
platform.Registry.add(Extensions.TokenClassificationContribution, tokenClassificationRegistry);
|
||||
|
||||
export function registerTokenType(id: string, description: string, scopesToProbe: ProbeScope[] = [], extendsTC: string | null = null, deprecationMessage?: string): string {
|
||||
tokenClassificationRegistry.registerTokenType(id, description, deprecationMessage);
|
||||
|
||||
if (scopesToProbe || extendsTC) {
|
||||
const classification = tokenClassificationRegistry.getTokenClassification(id, []);
|
||||
tokenClassificationRegistry.registerTokenStyleDefault(classification!, { scopesToProbe, light: extendsTC, dark: extendsTC, hc: extendsTC });
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export function registerTokenModifier(id: string, description: string, deprecationMessage?: string): string {
|
||||
tokenClassificationRegistry.registerTokenModifier(id, description, deprecationMessage);
|
||||
return id;
|
||||
}
|
||||
|
||||
export function getTokenClassificationRegistry(): ITokenClassificationRegistry {
|
||||
return tokenClassificationRegistry;
|
||||
}
|
||||
|
||||
export const comments = registerTokenType('comments', nls.localize('comments', "Style for comments."), [['comment']]);
|
||||
export const strings = registerTokenType('strings', nls.localize('strings', "Style for strings."), [['string']]);
|
||||
export const keywords = registerTokenType('keywords', nls.localize('keywords', "Style for keywords."), [['keyword.control']]);
|
||||
export const numbers = registerTokenType('numbers', nls.localize('numbers', "Style for numbers."), [['constant.numeric']]);
|
||||
export const regexp = registerTokenType('regexp', nls.localize('regexp', "Style for expressions."), [['constant.regexp']]);
|
||||
export const operators = registerTokenType('operators', nls.localize('operator', "Style for operators."), [['keyword.operator']]);
|
||||
|
||||
export const namespaces = registerTokenType('namespaces', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]);
|
||||
|
||||
export const types = registerTokenType('types', nls.localize('types', "Style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]);
|
||||
export const structs = registerTokenType('structs', nls.localize('struct', "Style for structs."), [['storage.type.struct']], types);
|
||||
export const classes = registerTokenType('classes', nls.localize('class', "Style for classes."), [['entity.name.class']], types);
|
||||
export const interfaces = registerTokenType('interfaces', nls.localize('interface', "Style for interfaces."), undefined, types);
|
||||
export const enums = registerTokenType('enums', nls.localize('enum', "Style for enums."), undefined, types);
|
||||
export const parameterTypes = registerTokenType('parameterTypes', nls.localize('parameterType', "Style for parameter types."), undefined, types);
|
||||
|
||||
export const functions = registerTokenType('functions', nls.localize('functions', "Style for functions"), [['entity.name.function'], ['support.function']]);
|
||||
export const macros = registerTokenType('macros', nls.localize('macro', "Style for macros."), undefined, functions);
|
||||
|
||||
export const variables = registerTokenType('variables', nls.localize('variables', "Style for variables."), [['variable'], ['entity.name.variable']]);
|
||||
export const constants = registerTokenType('constants', nls.localize('constants', "Style for constants."), undefined, variables);
|
||||
export const parameters = registerTokenType('parameters', nls.localize('parameters', "Style for parameters."), undefined, variables);
|
||||
export const property = registerTokenType('properties', nls.localize('properties', "Style for properties."), undefined, variables);
|
||||
|
||||
export const labels = registerTokenType('labels', nls.localize('labels', "Style for labels. "), undefined);
|
||||
|
||||
export const m_declaration = registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined);
|
||||
export const m_documentation = registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined);
|
||||
export const m_member = registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined);
|
||||
export const m_static = registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined);
|
||||
export const m_abstract = registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined);
|
||||
export const m_deprecated = registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined);
|
||||
export const m_modification = registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined);
|
||||
export const m_async = registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined);
|
||||
|
||||
function bitCount(u: number) {
|
||||
// https://blogs.msdn.microsoft.com/jeuge/2005/06/08/bit-fiddling-3/
|
||||
const uCount = u - ((u >> 1) & 0o33333333333) - ((u >> 2) & 0o11111111111);
|
||||
return ((uCount + (uCount >> 3)) & 0o30707070707) % 63;
|
||||
}
|
||||
|
||||
function getTokenStylingScore(classification: TokenClassification) {
|
||||
return bitCount(classification.modifiers) + ((classification.type !== TOKEN_TYPE_WILDCARD_NUM) ? 1 : 0);
|
||||
}
|
||||
|
||||
function getStylingSchemeEntry(description: string, deprecationMessage?: string): IJSONSchema {
|
||||
return {
|
||||
description,
|
||||
deprecationMessage,
|
||||
defaultSnippets: [{ body: '${1:#ff0000}' }],
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
format: 'color-hex'
|
||||
},
|
||||
{
|
||||
$ref: '#definitions/style'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
export const tokenStylingSchemaId = 'vscode://schemas/token-styling';
|
||||
|
||||
let schemaRegistry = platform.Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
|
||||
schemaRegistry.registerSchema(tokenStylingSchemaId, tokenClassificationRegistry.getTokenStylingSchema());
|
||||
|
||||
const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(tokenStylingSchemaId), 200);
|
||||
tokenClassificationRegistry.onDidChangeSchema(() => {
|
||||
if (!delayer.isScheduled()) {
|
||||
delayer.schedule();
|
||||
}
|
||||
});
|
|
@ -6,6 +6,7 @@
|
|||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IThemeService, ITheme, DARK, IIconTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { TokenStyle, TokenClassification, ProbeScope } from 'vs/platform/theme/common/tokenClassificationRegistry';
|
||||
|
||||
export class TestTheme implements ITheme {
|
||||
|
||||
|
@ -23,6 +24,14 @@ export class TestTheme implements ITheme {
|
|||
defines(color: string): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getTokenStyle(classification: TokenClassification, useDefault?: boolean | undefined): TokenStyle | undefined {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class TestIconTheme implements IIconTheme {
|
||||
|
|
|
@ -19,7 +19,10 @@ function getMockTheme(type: ThemeType): ITheme {
|
|||
label: '',
|
||||
type: type,
|
||||
getColor: (colorId: ColorIdentifier): Color | undefined => themingRegistry.resolveDefaultColor(colorId, theme),
|
||||
defines: () => true
|
||||
defines: () => true,
|
||||
getTokenStyle: () => undefined,
|
||||
resolveScopes: () => undefined
|
||||
|
||||
};
|
||||
return theme;
|
||||
}
|
||||
|
@ -99,4 +102,4 @@ suite('Workbench - TerminalColorRegistry', () => {
|
|||
'#e5e5e5'
|
||||
], 'The dark terminal colors should be used when a dark theme is active');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
|||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader';
|
||||
|
||||
class ExtensionResourceLoaderService implements IExtensionResourceLoaderService {
|
||||
export class ExtensionResourceLoaderService implements IExtensionResourceLoaderService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
|
|||
import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ITMSyntaxExtensionPoint, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars';
|
||||
import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService';
|
||||
import { ITokenColorizationRule, IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { ITextMateThemingRule, IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IGrammar, StackElement, IOnigLib, IRawTheme } from 'vscode-textmate';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -257,7 +257,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex
|
|||
TokenizationRegistry.setColorMap(colorMap);
|
||||
}
|
||||
|
||||
private static equalsTokenRules(a: ITokenColorizationRule[] | null, b: ITokenColorizationRule[] | null): boolean {
|
||||
private static equalsTokenRules(a: ITextMateThemingRule[] | null, b: ITextMateThemingRule[] | null): boolean {
|
||||
if (!b || !a || b.length !== a.length) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import * as nls from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
@ -29,6 +29,7 @@ import * as resources from 'vs/base/common/resources';
|
|||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema';
|
||||
import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
|
@ -92,6 +93,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
|||
return this.configurationService.getValue<ITokenColorCustomizations>(CUSTOM_EDITOR_COLORS_SETTING) || {};
|
||||
}
|
||||
|
||||
private get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations {
|
||||
return this.configurationService.getValue<IExperimentalTokenStyleCustomizations>(CUSTOM_EDITOR_TOKENSTYLES_SETTING) || {};
|
||||
}
|
||||
|
||||
constructor(
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
|
@ -126,6 +131,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
|||
}
|
||||
themeData.setCustomColors(this.colorCustomizations);
|
||||
themeData.setCustomTokenColors(this.tokenColorCustomizations);
|
||||
themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations);
|
||||
this.updateDynamicCSSRules(themeData);
|
||||
this.applyTheme(themeData, undefined, true);
|
||||
|
||||
|
@ -154,18 +160,22 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
|||
|
||||
const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} };
|
||||
const themeSpecificTokenColors: IJSONSchema = { properties: {} };
|
||||
const themeSpecificTokenStyling: IJSONSchema = { properties: {} };
|
||||
|
||||
const workbenchColors = { $ref: workbenchColorsSchemaId, additionalProperties: false };
|
||||
const tokenColors = { properties: tokenColorSchema.properties, additionalProperties: false };
|
||||
const tokenStyling = { $ref: tokenStylingSchemaId, additionalProperties: false };
|
||||
for (let t of event.themes) {
|
||||
// add theme specific color customization ("[Abyss]":{ ... })
|
||||
const themeId = `[${t.settingsId}]`;
|
||||
themeSpecificWorkbenchColors.properties![themeId] = workbenchColors;
|
||||
themeSpecificTokenColors.properties![themeId] = tokenColors;
|
||||
themeSpecificTokenStyling.properties![themeId] = tokenStyling;
|
||||
}
|
||||
|
||||
colorCustomizationsSchema.allOf![1] = themeSpecificWorkbenchColors;
|
||||
tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors;
|
||||
experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling;
|
||||
|
||||
configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration);
|
||||
|
||||
|
@ -308,6 +318,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
|||
this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations);
|
||||
hasColorChanges = true;
|
||||
}
|
||||
if (e.affectsConfiguration(CUSTOM_EDITOR_TOKENSTYLES_SETTING)) {
|
||||
this.currentColorTheme.setCustomTokenStyleRules(this.tokenStylesCustomizations);
|
||||
hasColorChanges = true;
|
||||
}
|
||||
if (hasColorChanges) {
|
||||
this.updateDynamicCSSRules(this.currentColorTheme);
|
||||
this.onColorThemeChange.fire(this.currentColorTheme);
|
||||
|
@ -698,12 +712,18 @@ const tokenColorCustomizationSchema: IConfigurationPropertySchema = {
|
|||
default: {},
|
||||
allOf: [tokenColorSchema]
|
||||
};
|
||||
const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = {
|
||||
description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."),
|
||||
default: {},
|
||||
allOf: [{ $ref: tokenStylingSchemaId }]
|
||||
};
|
||||
const tokenColorCustomizationConfiguration: IConfigurationNode = {
|
||||
id: 'editor',
|
||||
order: 7.2,
|
||||
type: 'object',
|
||||
properties: {
|
||||
[CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema
|
||||
[CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema,
|
||||
[CUSTOM_EDITOR_TOKENSTYLES_SETTING]: experimentalTokenStylingCustomizationSchema
|
||||
}
|
||||
};
|
||||
configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration);
|
||||
|
|
|
@ -6,22 +6,26 @@
|
|||
import { basename } from 'vs/base/common/path';
|
||||
import * as Json from 'vs/base/common/json';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { ExtensionData, ITokenColorCustomizations, ITokenColorizationRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, IExperimentalTokenStyleCustomizations, ITokenColorizationSetting } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { convertSettings } from 'vs/workbench/services/themes/common/themeCompatibility';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { Extensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Extensions as ColorRegistryExtensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ThemeType } from 'vs/platform/theme/common/themeService';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { TokenStyle, TokenClassification, ProbeScope, TokenStylingRule, getTokenClassificationRegistry } from 'vs/platform/theme/common/tokenClassificationRegistry';
|
||||
import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher';
|
||||
import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader';
|
||||
|
||||
let colorRegistry = Registry.as<IColorRegistry>(Extensions.ColorContribution);
|
||||
let colorRegistry = Registry.as<IColorRegistry>(ColorRegistryExtensions.ColorContribution);
|
||||
|
||||
let tokenClassificationRegistry = getTokenClassificationRegistry();
|
||||
|
||||
const tokenGroupToScopesMap = {
|
||||
comments: ['comment', 'punctuation.definition.comment'],
|
||||
|
@ -33,6 +37,7 @@ const tokenGroupToScopesMap = {
|
|||
variables: ['variable', 'entity.name.variable']
|
||||
};
|
||||
|
||||
|
||||
export class ColorThemeData implements IColorTheme {
|
||||
|
||||
id: string;
|
||||
|
@ -44,11 +49,17 @@ export class ColorThemeData implements IColorTheme {
|
|||
watch?: boolean;
|
||||
extensionData?: ExtensionData;
|
||||
|
||||
private themeTokenColors: ITokenColorizationRule[] = [];
|
||||
private customTokenColors: ITokenColorizationRule[] = [];
|
||||
private themeTokenColors: ITextMateThemingRule[] = [];
|
||||
private customTokenColors: ITextMateThemingRule[] = [];
|
||||
private colorMap: IColorMap = {};
|
||||
private customColorMap: IColorMap = {};
|
||||
|
||||
private tokenStylingRules: TokenStylingRule[] | undefined = undefined;
|
||||
private customTokenStylingRules: TokenStylingRule[] = [];
|
||||
|
||||
private themeTokenScopeMatchers: Matcher<ProbeScope>[] | undefined;
|
||||
private customTokenScopeMatchers: Matcher<ProbeScope>[] | undefined;
|
||||
|
||||
private constructor(id: string, label: string, settingsId: string) {
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
|
@ -56,8 +67,8 @@ export class ColorThemeData implements IColorTheme {
|
|||
this.isLoaded = false;
|
||||
}
|
||||
|
||||
get tokenColors(): ITokenColorizationRule[] {
|
||||
const result: ITokenColorizationRule[] = [];
|
||||
get tokenColors(): ITextMateThemingRule[] {
|
||||
const result: ITextMateThemingRule[] = [];
|
||||
|
||||
// the default rule (scope empty) is always the first rule. Ignore all other default rules.
|
||||
const foreground = this.getColor(editorForeground) || this.getDefault(editorForeground)!;
|
||||
|
@ -71,7 +82,7 @@ export class ColorThemeData implements IColorTheme {
|
|||
|
||||
let hasDefaultTokens = false;
|
||||
|
||||
function addRule(rule: ITokenColorizationRule) {
|
||||
function addRule(rule: ITextMateThemingRule) {
|
||||
if (rule.scope && rule.settings) {
|
||||
if (rule.scope === 'token.info-token') {
|
||||
hasDefaultTokens = true;
|
||||
|
@ -103,10 +114,57 @@ export class ColorThemeData implements IColorTheme {
|
|||
return color;
|
||||
}
|
||||
|
||||
public getTokenStyle(tokenClassification: TokenClassification, useDefault?: boolean): TokenStyle | undefined {
|
||||
// todo: cache results
|
||||
return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, this.tokenStylingRules, this.customTokenStylingRules, this);
|
||||
}
|
||||
|
||||
public getDefault(colorId: ColorIdentifier): Color | undefined {
|
||||
return colorRegistry.resolveDefaultColor(colorId, this);
|
||||
}
|
||||
|
||||
public getDefaultTokenStyle(tokenClassification: TokenClassification): TokenStyle | undefined {
|
||||
return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, undefined, [], this);
|
||||
}
|
||||
|
||||
public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined {
|
||||
|
||||
if (!this.themeTokenScopeMatchers) {
|
||||
this.themeTokenScopeMatchers = this.themeTokenColors.map(getScopeMatcher);
|
||||
}
|
||||
if (!this.customTokenScopeMatchers) {
|
||||
this.customTokenScopeMatchers = this.customTokenColors.map(getScopeMatcher);
|
||||
}
|
||||
|
||||
for (let scope of scopes) {
|
||||
let foreground: string | undefined = undefined;
|
||||
let fontStyle: string | undefined = undefined;
|
||||
let foregroundScore = -1;
|
||||
let fontStyleScore = -1;
|
||||
|
||||
function findTokenStyleForScopeInScopes(scopeMatchers: Matcher<ProbeScope>[], tokenColors: ITextMateThemingRule[]) {
|
||||
for (let i = 0; i < scopeMatchers.length; i++) {
|
||||
const score = scopeMatchers[i](scope);
|
||||
if (score >= 0) {
|
||||
const settings = tokenColors[i].settings;
|
||||
if (score >= foregroundScore && settings.foreground) {
|
||||
foreground = settings.foreground;
|
||||
}
|
||||
if (score >= fontStyleScore && types.isString(settings.fontStyle)) {
|
||||
fontStyle = settings.fontStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors);
|
||||
findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors);
|
||||
if (foreground !== undefined || fontStyle !== undefined) {
|
||||
return getTokenStyle(foreground, fontStyle);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public defines(colorId: ColorIdentifier): boolean {
|
||||
return this.customColorMap.hasOwnProperty(colorId) || this.colorMap.hasOwnProperty(colorId);
|
||||
}
|
||||
|
@ -132,6 +190,7 @@ export class ColorThemeData implements IColorTheme {
|
|||
|
||||
public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) {
|
||||
this.customTokenColors = [];
|
||||
this.customTokenScopeMatchers = undefined;
|
||||
// first add the non-theme specific settings
|
||||
this.addCustomTokenColors(customTokenColors);
|
||||
|
||||
|
@ -142,6 +201,16 @@ export class ColorThemeData implements IColorTheme {
|
|||
}
|
||||
}
|
||||
|
||||
public setCustomTokenStyleRules(tokenStylingRules: IExperimentalTokenStyleCustomizations) {
|
||||
this.tokenStylingRules = [];
|
||||
readCustomTokenStyleRules(tokenStylingRules, this.tokenStylingRules);
|
||||
|
||||
const themeSpecificColors = tokenStylingRules[`[${this.settingsId}]`] as IExperimentalTokenStyleCustomizations;
|
||||
if (types.isObject(themeSpecificColors)) {
|
||||
readCustomTokenStyleRules(themeSpecificColors, this.tokenStylingRules);
|
||||
}
|
||||
}
|
||||
|
||||
private addCustomTokenColors(customTokenColors: ITokenColorCustomizations) {
|
||||
// Put the general customizations such as comments, strings, etc. first so that
|
||||
// they can be overridden by specific customizations like "string.interpolated"
|
||||
|
@ -180,9 +249,18 @@ export class ColorThemeData implements IColorTheme {
|
|||
return Promise.resolve(undefined);
|
||||
}
|
||||
this.themeTokenColors = [];
|
||||
this.colorMap = {};
|
||||
return _loadColorTheme(extensionResourceLoaderService, this.location, this.themeTokenColors, this.colorMap).then(_ => {
|
||||
this.themeTokenScopeMatchers = undefined;
|
||||
|
||||
const result = {
|
||||
colors: {},
|
||||
textMateRules: [],
|
||||
stylingRules: undefined
|
||||
};
|
||||
return _loadColorTheme(extensionResourceLoaderService, this.location, result).then(_ => {
|
||||
this.isLoaded = true;
|
||||
this.tokenStylingRules = result.stylingRules;
|
||||
this.colorMap = result.colors;
|
||||
this.themeTokenColors = result.textMateRules;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -295,7 +373,7 @@ function toCSSSelector(extensionId: string, path: string) {
|
|||
return str;
|
||||
}
|
||||
|
||||
function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise<any> {
|
||||
function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[] | undefined }): Promise<any> {
|
||||
if (resources.extname(themeLocation) === '.json') {
|
||||
return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => {
|
||||
let errors: Json.ParseError[] = [];
|
||||
|
@ -307,11 +385,11 @@ function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoade
|
|||
}
|
||||
let includeCompletes: Promise<any> = Promise.resolve(null);
|
||||
if (contentValue.include) {
|
||||
includeCompletes = _loadColorTheme(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), resultRules, resultColors);
|
||||
includeCompletes = _loadColorTheme(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), result);
|
||||
}
|
||||
return includeCompletes.then(_ => {
|
||||
if (Array.isArray(contentValue.settings)) {
|
||||
convertSettings(contentValue.settings, resultRules, resultColors);
|
||||
convertSettings(contentValue.settings, result);
|
||||
return null;
|
||||
}
|
||||
let colors = contentValue.colors;
|
||||
|
@ -323,38 +401,42 @@ function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoade
|
|||
for (let colorId in colors) {
|
||||
let colorHex = colors[colorId];
|
||||
if (typeof colorHex === 'string') { // ignore colors tht are null
|
||||
resultColors[colorId] = Color.fromHex(colors[colorId]);
|
||||
result.colors[colorId] = Color.fromHex(colors[colorId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
let tokenColors = contentValue.tokenColors;
|
||||
if (tokenColors) {
|
||||
if (Array.isArray(tokenColors)) {
|
||||
resultRules.push(...tokenColors);
|
||||
result.textMateRules.push(...tokenColors);
|
||||
return null;
|
||||
} else if (typeof tokenColors === 'string') {
|
||||
return _loadSyntaxTokens(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), tokenColors), resultRules, {});
|
||||
return _loadSyntaxTokens(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), tokenColors), result);
|
||||
} else {
|
||||
return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.tokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'tokenColors' should be either an array specifying colors or a path to a TextMate theme file", themeLocation.toString())));
|
||||
}
|
||||
}
|
||||
let tokenStylingRules = contentValue.tokenStylingRules;
|
||||
if (tokenStylingRules && typeof tokenStylingRules === 'object') {
|
||||
result.stylingRules = readCustomTokenStyleRules(tokenStylingRules, result.stylingRules);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return _loadSyntaxTokens(extensionResourceLoaderService, themeLocation, resultRules, resultColors);
|
||||
return _loadSyntaxTokens(extensionResourceLoaderService, themeLocation, result);
|
||||
}
|
||||
}
|
||||
|
||||
function _loadSyntaxTokens(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise<any> {
|
||||
function _loadSyntaxTokens(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap }): Promise<any> {
|
||||
return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => {
|
||||
try {
|
||||
let contentValue = parsePList(content);
|
||||
let settings: ITokenColorizationRule[] = contentValue.settings;
|
||||
let settings: ITextMateThemingRule[] = contentValue.settings;
|
||||
if (!Array.isArray(settings)) {
|
||||
return Promise.reject(new Error(nls.localize('error.plist.invalidformat', "Problem parsing tmTheme file: {0}. 'settings' is not array.")));
|
||||
}
|
||||
convertSettings(settings, resultRules, resultColors);
|
||||
convertSettings(settings, result);
|
||||
return Promise.resolve(null);
|
||||
} catch (e) {
|
||||
return Promise.reject(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message)));
|
||||
|
@ -364,7 +446,7 @@ function _loadSyntaxTokens(extensionResourceLoaderService: IExtensionResourceLoa
|
|||
});
|
||||
}
|
||||
|
||||
let defaultThemeColors: { [baseTheme: string]: ITokenColorizationRule[] } = {
|
||||
let defaultThemeColors: { [baseTheme: string]: ITextMateThemingRule[] } = {
|
||||
'light': [
|
||||
{ scope: 'token.info-token', settings: { foreground: '#316bcd' } },
|
||||
{ scope: 'token.warn-token', settings: { foreground: '#cd9731' } },
|
||||
|
@ -384,3 +466,124 @@ let defaultThemeColors: { [baseTheme: string]: ITokenColorizationRule[] } = {
|
|||
{ scope: 'token.debug-token', settings: { foreground: '#b267e6' } }
|
||||
],
|
||||
};
|
||||
|
||||
const noMatch = (_scope: ProbeScope) => -1;
|
||||
|
||||
function nameMatcher(identifers: string[], scope: ProbeScope): number {
|
||||
function findInIdents(s: string, lastIndent: number): number {
|
||||
for (let i = lastIndent - 1; i >= 0; i--) {
|
||||
if (scopesAreMatching(s, identifers[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (scope.length < identifers.length) {
|
||||
return -1;
|
||||
}
|
||||
let lastScopeIndex = scope.length - 1;
|
||||
let lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], identifers.length);
|
||||
if (lastIdentifierIndex >= 0) {
|
||||
const score = (lastIdentifierIndex + 1) * 0x10000 + scope.length;
|
||||
while (lastScopeIndex >= 0) {
|
||||
lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], lastIdentifierIndex);
|
||||
if (lastIdentifierIndex === -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return score;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
function scopesAreMatching(thisScopeName: string, scopeName: string): boolean {
|
||||
if (!thisScopeName) {
|
||||
return false;
|
||||
}
|
||||
if (thisScopeName === scopeName) {
|
||||
return true;
|
||||
}
|
||||
const len = scopeName.length;
|
||||
return thisScopeName.length > len && thisScopeName.substr(0, len) === scopeName && thisScopeName[len] === '.';
|
||||
}
|
||||
|
||||
function getScopeMatcher(rule: ITextMateThemingRule): Matcher<ProbeScope> {
|
||||
const ruleScope = rule.scope;
|
||||
if (!ruleScope || !rule.settings) {
|
||||
return noMatch;
|
||||
}
|
||||
const matchers: MatcherWithPriority<ProbeScope>[] = [];
|
||||
if (Array.isArray(ruleScope)) {
|
||||
for (let rs of ruleScope) {
|
||||
createMatchers(rs, nameMatcher, matchers);
|
||||
}
|
||||
} else {
|
||||
createMatchers(ruleScope, nameMatcher, matchers);
|
||||
}
|
||||
|
||||
if (matchers.length === 0) {
|
||||
return noMatch;
|
||||
}
|
||||
return (scope: ProbeScope) => {
|
||||
let max = matchers[0].matcher(scope);
|
||||
for (let i = 1; i < matchers.length; i++) {
|
||||
max = Math.max(max, matchers[i].matcher(scope));
|
||||
}
|
||||
return max;
|
||||
};
|
||||
}
|
||||
|
||||
function getTokenStyle(foreground: string | undefined, fontStyle: string | undefined): TokenStyle {
|
||||
let foregroundColor = undefined;
|
||||
if (foreground !== undefined) {
|
||||
foregroundColor = Color.fromHex(foreground);
|
||||
}
|
||||
let bold, underline, italic;
|
||||
if (fontStyle !== undefined) {
|
||||
fontStyle = fontStyle.trim();
|
||||
if (fontStyle.length === 0) {
|
||||
bold = italic = underline = false;
|
||||
} else {
|
||||
const expression = /-?italic|-?bold|-?underline/g;
|
||||
let match;
|
||||
while ((match = expression.exec(fontStyle))) {
|
||||
switch (match[0]) {
|
||||
case 'bold': bold = true; break;
|
||||
case '-bold': bold = false; break;
|
||||
case 'italic': italic = true; break;
|
||||
case '-italic': italic = false; break;
|
||||
case 'underline': underline = true; break;
|
||||
case '-underline': underline = false; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new TokenStyle(foregroundColor, bold, underline, italic);
|
||||
|
||||
}
|
||||
|
||||
function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenStyleCustomizations, result: TokenStylingRule[] = []) {
|
||||
for (let key in tokenStylingRuleSection) {
|
||||
if (key[0] !== '[') {
|
||||
const classification = tokenClassificationRegistry.getTokenClassificationFromString(key);
|
||||
if (classification) {
|
||||
const settings = tokenStylingRuleSection[key];
|
||||
let style: TokenStyle | undefined;
|
||||
if (typeof settings === 'string') {
|
||||
style = getTokenStyle(settings, undefined);
|
||||
} else if (isTokenColorizationSetting(settings)) {
|
||||
style = getTokenStyle(settings.foreground, settings.fontStyle);
|
||||
}
|
||||
if (style) {
|
||||
result.push(tokenClassificationRegistry.getTokenStylingRule(classification, style));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function isTokenColorizationSetting(style: any): style is ITokenColorizationSetting {
|
||||
return style && (style.foreground || style.fontStyle);
|
||||
}
|
||||
|
|
134
src/vs/workbench/services/themes/common/textMateScopeMatcher.ts
Normal file
134
src/vs/workbench/services/themes/common/textMateScopeMatcher.ts
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export interface MatcherWithPriority<T> {
|
||||
matcher: Matcher<T>;
|
||||
priority: -1 | 0 | 1;
|
||||
}
|
||||
|
||||
export interface Matcher<T> {
|
||||
(matcherInput: T): number;
|
||||
}
|
||||
|
||||
export function createMatchers<T>(selector: string, matchesName: (names: string[], matcherInput: T) => number, results: MatcherWithPriority<T>[]): void {
|
||||
const tokenizer = newTokenizer(selector);
|
||||
let token = tokenizer.next();
|
||||
while (token !== null) {
|
||||
let priority: -1 | 0 | 1 = 0;
|
||||
if (token.length === 2 && token.charAt(1) === ':') {
|
||||
switch (token.charAt(0)) {
|
||||
case 'R': priority = 1; break;
|
||||
case 'L': priority = -1; break;
|
||||
default:
|
||||
console.log(`Unknown priority ${token} in scope selector`);
|
||||
}
|
||||
token = tokenizer.next();
|
||||
}
|
||||
let matcher = parseConjunction();
|
||||
if (matcher) {
|
||||
results.push({ matcher, priority });
|
||||
}
|
||||
if (token !== ',') {
|
||||
break;
|
||||
}
|
||||
token = tokenizer.next();
|
||||
}
|
||||
|
||||
function parseOperand(): Matcher<T> | null {
|
||||
if (token === '-') {
|
||||
token = tokenizer.next();
|
||||
const expressionToNegate = parseOperand();
|
||||
if (!expressionToNegate) {
|
||||
return null;
|
||||
}
|
||||
return matcherInput => {
|
||||
const score = expressionToNegate(matcherInput);
|
||||
return score < 0 ? 0 : -1;
|
||||
};
|
||||
}
|
||||
if (token === '(') {
|
||||
token = tokenizer.next();
|
||||
const expressionInParents = parseInnerExpression();
|
||||
if (token === ')') {
|
||||
token = tokenizer.next();
|
||||
}
|
||||
return expressionInParents;
|
||||
}
|
||||
if (isIdentifier(token)) {
|
||||
const identifiers: string[] = [];
|
||||
do {
|
||||
identifiers.push(token);
|
||||
token = tokenizer.next();
|
||||
} while (isIdentifier(token));
|
||||
return matcherInput => matchesName(identifiers, matcherInput);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function parseConjunction(): Matcher<T> | null {
|
||||
let matcher = parseOperand();
|
||||
if (!matcher) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const matchers: Matcher<T>[] = [];
|
||||
while (matcher) {
|
||||
matchers.push(matcher);
|
||||
matcher = parseOperand();
|
||||
}
|
||||
return matcherInput => { // and
|
||||
let min = matchers[0](matcherInput);
|
||||
for (let i = 1; min >= 0 && i < matchers.length; i++) {
|
||||
min = Math.min(min, matchers[i](matcherInput));
|
||||
}
|
||||
return min;
|
||||
};
|
||||
}
|
||||
function parseInnerExpression(): Matcher<T> | null {
|
||||
let matcher = parseConjunction();
|
||||
if (!matcher) {
|
||||
return null;
|
||||
}
|
||||
const matchers: Matcher<T>[] = [];
|
||||
while (matcher) {
|
||||
matchers.push(matcher);
|
||||
if (token === '|' || token === ',') {
|
||||
do {
|
||||
token = tokenizer.next();
|
||||
} while (token === '|' || token === ','); // ignore subsequent commas
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
matcher = parseConjunction();
|
||||
}
|
||||
return matcherInput => { // or
|
||||
let max = matchers[0](matcherInput);
|
||||
for (let i = 1; i < matchers.length; i++) {
|
||||
max = Math.max(max, matchers[i](matcherInput));
|
||||
}
|
||||
return max;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function isIdentifier(token: string | null): token is string {
|
||||
return !!token && !!token.match(/[\w\.:]+/);
|
||||
}
|
||||
|
||||
function newTokenizer(input: string): { next: () => string | null } {
|
||||
let regex = /([LR]:|[\w\.:][\w\.:\-]*|[\,\|\-\(\)])/g;
|
||||
let match = regex.exec(input);
|
||||
return {
|
||||
next: () => {
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const res = match[0];
|
||||
match = regex.exec(input);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITokenColorizationRule, IColorMap } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { ITextMateThemingRule, IColorMap } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
|
@ -18,9 +18,9 @@ function addSettingMapping(settingId: string, colorId: string) {
|
|||
colorIds.push(colorId);
|
||||
}
|
||||
|
||||
export function convertSettings(oldSettings: ITokenColorizationRule[], resultRules: ITokenColorizationRule[], resultColors: IColorMap): void {
|
||||
export function convertSettings(oldSettings: ITextMateThemingRule[], result: { textMateRules: ITextMateThemingRule[], colors: IColorMap }): void {
|
||||
for (let rule of oldSettings) {
|
||||
resultRules.push(rule);
|
||||
result.textMateRules.push(rule);
|
||||
if (!rule.scope) {
|
||||
let settings = rule.settings;
|
||||
if (!settings) {
|
||||
|
@ -34,7 +34,7 @@ export function convertSettings(oldSettings: ITokenColorizationRule[], resultRul
|
|||
if (typeof colorHex === 'string') {
|
||||
let color = Color.fromHex(colorHex);
|
||||
for (let colorId of mappings) {
|
||||
resultColors[colorId] = color;
|
||||
result.colors[colorId] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ export const DETECT_HC_SETTING = 'window.autoDetectHighContrast';
|
|||
export const ICON_THEME_SETTING = 'workbench.iconTheme';
|
||||
export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations';
|
||||
export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations';
|
||||
export const CUSTOM_EDITOR_TOKENSTYLES_SETTING = 'editor.tokenColorCustomizationsExperimental';
|
||||
|
||||
export interface IColorTheme extends ITheme {
|
||||
readonly id: string;
|
||||
|
@ -30,7 +31,7 @@ export interface IColorTheme extends ITheme {
|
|||
readonly extensionData?: ExtensionData;
|
||||
readonly description?: string;
|
||||
readonly isLoaded: boolean;
|
||||
readonly tokenColors: ITokenColorizationRule[];
|
||||
readonly tokenColors: ITextMateThemingRule[];
|
||||
}
|
||||
|
||||
export interface IColorMap {
|
||||
|
@ -69,7 +70,7 @@ export interface IColorCustomizations {
|
|||
}
|
||||
|
||||
export interface ITokenColorCustomizations {
|
||||
[groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITokenColorizationRule[];
|
||||
[groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[];
|
||||
comments?: string | ITokenColorizationSetting;
|
||||
strings?: string | ITokenColorizationSetting;
|
||||
numbers?: string | ITokenColorizationSetting;
|
||||
|
@ -77,10 +78,14 @@ export interface ITokenColorCustomizations {
|
|||
types?: string | ITokenColorizationSetting;
|
||||
functions?: string | ITokenColorizationSetting;
|
||||
variables?: string | ITokenColorizationSetting;
|
||||
textMateRules?: ITokenColorizationRule[];
|
||||
textMateRules?: ITextMateThemingRule[];
|
||||
}
|
||||
|
||||
export interface ITokenColorizationRule {
|
||||
export interface IExperimentalTokenStyleCustomizations {
|
||||
[styleRuleOrThemeSettingsId: string]: string | ITokenColorizationSetting | IExperimentalTokenStyleCustomizations | undefined;
|
||||
}
|
||||
|
||||
export interface ITextMateThemingRule {
|
||||
name?: string;
|
||||
scope?: string | string[];
|
||||
settings: ITokenColorizationSetting;
|
||||
|
@ -89,7 +94,7 @@ export interface ITokenColorizationRule {
|
|||
export interface ITokenColorizationSetting {
|
||||
foreground?: string;
|
||||
background?: string;
|
||||
fontStyle?: string; // italic, underline, bold
|
||||
fontStyle?: string; /* [italic|underline|bold] */
|
||||
}
|
||||
|
||||
export interface ExtensionData {
|
||||
|
@ -106,4 +111,4 @@ export interface IThemeExtensionPoint {
|
|||
path: string;
|
||||
uiTheme?: typeof VS_LIGHT_THEME | typeof VS_DARK_THEME | typeof VS_HC_THEME;
|
||||
_watch: boolean; // unsupported options to watch location
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,294 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData';
|
||||
import * as assert from 'assert';
|
||||
import { ITokenColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { TokenStyle, comments, variables, types, functions, keywords, numbers, strings, getTokenClassificationRegistry } from 'vs/platform/theme/common/tokenClassificationRegistry';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { ExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService';
|
||||
|
||||
let tokenClassificationRegistry = getTokenClassificationRegistry();
|
||||
|
||||
const undefinedStyle = { bold: undefined, underline: undefined, italic: undefined };
|
||||
const unsetStyle = { bold: false, underline: false, italic: false };
|
||||
|
||||
function ts(foreground: string | undefined, styleFlags: { bold?: boolean; underline?: boolean; italic?: boolean } | undefined): TokenStyle {
|
||||
const foregroundColor = isString(foreground) ? Color.fromHex(foreground) : undefined;
|
||||
return new TokenStyle(foregroundColor, styleFlags && styleFlags.bold, styleFlags && styleFlags.underline, styleFlags && styleFlags.italic);
|
||||
}
|
||||
|
||||
function tokenStyleAsString(ts: TokenStyle | undefined | null) {
|
||||
if (!ts) {
|
||||
return 'tokenstyle-undefined';
|
||||
}
|
||||
let str = ts.foreground ? ts.foreground.toString() : 'no-foreground';
|
||||
if (ts.bold !== undefined) {
|
||||
str += ts.bold ? '+B' : '-B';
|
||||
}
|
||||
if (ts.underline !== undefined) {
|
||||
str += ts.underline ? '+U' : '-U';
|
||||
}
|
||||
if (ts.italic !== undefined) {
|
||||
str += ts.italic ? '+I' : '-I';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function assertTokenStyle(actual: TokenStyle | undefined | null, expected: TokenStyle | undefined | null, message?: string) {
|
||||
assert.equal(tokenStyleAsString(actual), tokenStyleAsString(expected), message);
|
||||
}
|
||||
|
||||
function assertTokenStyles(themeData: ColorThemeData, expected: { [qualifiedClassifier: string]: TokenStyle }) {
|
||||
for (let qualifiedClassifier in expected) {
|
||||
const classification = tokenClassificationRegistry.getTokenClassificationFromString(qualifiedClassifier);
|
||||
assert.ok(classification, 'Classification not found');
|
||||
|
||||
const tokenStyle = themeData.getTokenStyle(classification!);
|
||||
assertTokenStyle(tokenStyle, expected[qualifiedClassifier], qualifiedClassifier);
|
||||
}
|
||||
}
|
||||
|
||||
suite('Themes - TokenStyleResolving', () => {
|
||||
|
||||
|
||||
const fileService = new FileService(new NullLogService());
|
||||
const extensionResourceLoaderService = new ExtensionResourceLoaderService(fileService);
|
||||
|
||||
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
|
||||
|
||||
test('color defaults - monokai', async () => {
|
||||
const themeData = ColorThemeData.createUnloadedTheme('foo');
|
||||
const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-monokai/themes/monokai-color-theme.json');
|
||||
themeData.location = URI.file(themeLocation);
|
||||
await themeData.ensureLoaded(extensionResourceLoaderService);
|
||||
|
||||
assert.equal(themeData.isLoaded, true);
|
||||
|
||||
assertTokenStyles(themeData, {
|
||||
[comments]: ts('#88846f', undefinedStyle),
|
||||
[variables]: ts('#F8F8F2', unsetStyle),
|
||||
[types]: ts('#A6E22E', { underline: true }),
|
||||
[functions]: ts('#A6E22E', unsetStyle),
|
||||
[strings]: ts('#E6DB74', undefinedStyle),
|
||||
[numbers]: ts('#AE81FF', undefinedStyle),
|
||||
[keywords]: ts('#F92672', undefinedStyle)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('color defaults - dark+', async () => {
|
||||
const themeData = ColorThemeData.createUnloadedTheme('foo');
|
||||
const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/dark_plus.json');
|
||||
themeData.location = URI.file(themeLocation);
|
||||
await themeData.ensureLoaded(extensionResourceLoaderService);
|
||||
|
||||
assert.equal(themeData.isLoaded, true);
|
||||
|
||||
assertTokenStyles(themeData, {
|
||||
[comments]: ts('#6A9955', undefinedStyle),
|
||||
[variables]: ts('#9CDCFE', undefinedStyle),
|
||||
[types]: ts('#4EC9B0', undefinedStyle),
|
||||
[functions]: ts('#DCDCAA', undefinedStyle),
|
||||
[strings]: ts('#CE9178', undefinedStyle),
|
||||
[numbers]: ts('#B5CEA8', undefinedStyle),
|
||||
[keywords]: ts('#C586C0', undefinedStyle)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('color defaults - light vs', async () => {
|
||||
const themeData = ColorThemeData.createUnloadedTheme('foo');
|
||||
const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/light_vs.json');
|
||||
themeData.location = URI.file(themeLocation);
|
||||
await themeData.ensureLoaded(extensionResourceLoaderService);
|
||||
|
||||
assert.equal(themeData.isLoaded, true);
|
||||
|
||||
assertTokenStyles(themeData, {
|
||||
[comments]: ts('#008000', undefinedStyle),
|
||||
[variables]: ts(undefined, undefinedStyle),
|
||||
[types]: ts(undefined, undefinedStyle),
|
||||
[functions]: ts(undefined, undefinedStyle),
|
||||
[strings]: ts('#a31515', undefinedStyle),
|
||||
[numbers]: ts('#09885a', undefinedStyle),
|
||||
[keywords]: ts('#0000ff', undefinedStyle)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('color defaults - hc', async () => {
|
||||
const themeData = ColorThemeData.createUnloadedTheme('foo');
|
||||
const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/hc_black.json');
|
||||
themeData.location = URI.file(themeLocation);
|
||||
await themeData.ensureLoaded(extensionResourceLoaderService);
|
||||
|
||||
assert.equal(themeData.isLoaded, true);
|
||||
|
||||
assertTokenStyles(themeData, {
|
||||
[comments]: ts('#7ca668', undefinedStyle),
|
||||
[variables]: ts('#9CDCFE', undefinedStyle),
|
||||
[types]: ts('#4EC9B0', undefinedStyle),
|
||||
[functions]: ts('#DCDCAA', undefinedStyle),
|
||||
[strings]: ts('#ce9178', undefinedStyle),
|
||||
[numbers]: ts('#b5cea8', undefinedStyle),
|
||||
[keywords]: ts('#C586C0', undefinedStyle)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('color defaults - kimbie dark', async () => {
|
||||
const themeData = ColorThemeData.createUnloadedTheme('foo');
|
||||
const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json');
|
||||
themeData.location = URI.file(themeLocation);
|
||||
await themeData.ensureLoaded(extensionResourceLoaderService);
|
||||
|
||||
assert.equal(themeData.isLoaded, true);
|
||||
|
||||
assertTokenStyles(themeData, {
|
||||
[comments]: ts('#a57a4c', undefinedStyle),
|
||||
[variables]: ts('#dc3958', undefinedStyle),
|
||||
[types]: ts('#f06431', undefinedStyle),
|
||||
[functions]: ts('#8ab1b0', undefinedStyle),
|
||||
[strings]: ts('#889b4a', undefinedStyle),
|
||||
[numbers]: ts('#f79a32', undefinedStyle),
|
||||
[keywords]: ts('#98676a', undefinedStyle)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('color defaults - abyss', async () => {
|
||||
const themeData = ColorThemeData.createUnloadedTheme('foo');
|
||||
const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-abyss/themes/abyss-color-theme.json');
|
||||
themeData.location = URI.file(themeLocation);
|
||||
await themeData.ensureLoaded(extensionResourceLoaderService);
|
||||
|
||||
assert.equal(themeData.isLoaded, true);
|
||||
|
||||
assertTokenStyles(themeData, {
|
||||
[comments]: ts('#384887', undefinedStyle),
|
||||
[variables]: ts(undefined, unsetStyle),
|
||||
[types]: ts('#ffeebb', { underline: true }),
|
||||
[functions]: ts('#ddbb88', unsetStyle),
|
||||
[strings]: ts('#22aa44', undefinedStyle),
|
||||
[numbers]: ts('#f280d0', undefinedStyle),
|
||||
[keywords]: ts('#225588', undefinedStyle)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('resolveScopes', async () => {
|
||||
const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test');
|
||||
|
||||
const customTokenColors: ITokenColorCustomizations = {
|
||||
textMateRules: [
|
||||
{
|
||||
scope: 'variable',
|
||||
settings: {
|
||||
fontStyle: '',
|
||||
foreground: '#F8F8F2'
|
||||
}
|
||||
},
|
||||
{
|
||||
scope: 'keyword.operator',
|
||||
settings: {
|
||||
fontStyle: 'italic bold underline',
|
||||
foreground: '#F92672'
|
||||
}
|
||||
},
|
||||
{
|
||||
scope: 'storage',
|
||||
settings: {
|
||||
fontStyle: 'italic',
|
||||
foreground: '#F92672'
|
||||
}
|
||||
},
|
||||
{
|
||||
scope: ['storage.type', 'meta.structure.dictionary.json string.quoted.double.json'],
|
||||
settings: {
|
||||
foreground: '#66D9EF'
|
||||
}
|
||||
},
|
||||
{
|
||||
scope: 'entity.name.type, entity.name.class, entity.name.namespace, entity.name.scope-resolution',
|
||||
settings: {
|
||||
fontStyle: 'underline',
|
||||
foreground: '#A6E22E'
|
||||
}
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
themeData.setCustomTokenColors(customTokenColors);
|
||||
|
||||
let tokenStyle;
|
||||
let defaultTokenStyle = undefined;
|
||||
|
||||
tokenStyle = themeData.resolveScopes([['variable']]);
|
||||
assertTokenStyle(tokenStyle, ts('#F8F8F2', unsetStyle), 'variable');
|
||||
|
||||
tokenStyle = themeData.resolveScopes([['keyword.operator']]);
|
||||
assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, bold: true, underline: true }), 'keyword');
|
||||
|
||||
tokenStyle = themeData.resolveScopes([['keyword']]);
|
||||
assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword');
|
||||
|
||||
tokenStyle = themeData.resolveScopes([['keyword.operator']]);
|
||||
assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, bold: true, underline: true }), 'keyword.operator');
|
||||
|
||||
tokenStyle = themeData.resolveScopes([['keyword.operators']]);
|
||||
assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword.operators');
|
||||
|
||||
tokenStyle = themeData.resolveScopes([['storage']]);
|
||||
assertTokenStyle(tokenStyle, ts('#F92672', { italic: true }), 'storage');
|
||||
|
||||
tokenStyle = themeData.resolveScopes([['storage.type']]);
|
||||
assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type');
|
||||
|
||||
tokenStyle = themeData.resolveScopes([['entity.name.class']]);
|
||||
assertTokenStyle(tokenStyle, ts('#A6E22E', { underline: true }), 'entity.name.class');
|
||||
|
||||
tokenStyle = themeData.resolveScopes([['meta.structure.dictionary.json', 'string.quoted.double.json']]);
|
||||
assertTokenStyle(tokenStyle, ts('#66D9EF', undefined), 'json property');
|
||||
|
||||
tokenStyle = themeData.resolveScopes([['keyword'], ['storage.type'], ['entity.name.class']]);
|
||||
assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type');
|
||||
|
||||
});
|
||||
|
||||
test('rule matching', async () => {
|
||||
const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test');
|
||||
themeData.setCustomColors({ 'editor.foreground': '#000000' });
|
||||
themeData.setCustomTokenStyleRules({
|
||||
'types': '#ff0000',
|
||||
'classes': { foreground: '#0000ff', fontStyle: 'italic' },
|
||||
'*.static': { fontStyle: 'bold' },
|
||||
'*.declaration': { fontStyle: 'italic' },
|
||||
'*.async.static': { fontStyle: 'italic underline' },
|
||||
'*.async': { foreground: '#000fff', fontStyle: '-italic underline' }
|
||||
});
|
||||
|
||||
assertTokenStyles(themeData, {
|
||||
'types': ts('#ff0000', undefinedStyle),
|
||||
'types.static': ts('#ff0000', { bold: true }),
|
||||
'types.static.declaration': ts('#ff0000', { bold: true, italic: true }),
|
||||
'classes': ts('#0000ff', { italic: true }),
|
||||
'classes.static.declaration': ts('#0000ff', { bold: true, italic: true }),
|
||||
'classes.declaration': ts('#0000ff', { italic: true }),
|
||||
'classes.declaration.async': ts('#000fff', { underline: true, italic: false }),
|
||||
'classes.declaration.async.static': ts('#000fff', { italic: true, underline: true, bold: true }),
|
||||
});
|
||||
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue