semantic theming: add TokenSelector

This commit is contained in:
Martin Aeschlimann
2020-02-06 22:33:44 +01:00
parent 5d1c4a9b10
commit 7a036679a0
4 changed files with 86 additions and 57 deletions
@@ -26,6 +26,11 @@ export interface TokenClassification {
modifiers: number;
}
export interface TokenSelector {
match(classification: TokenClassification): number;
asString(): string;
}
export interface TokenTypeOrModifierContribution {
readonly num: number;
readonly id: string;
@@ -96,15 +101,13 @@ export interface TokenStyleDefaults {
}
export interface TokenStylingDefaultRule {
match(classification: TokenClassification): number;
selector: TokenClassification;
selector: TokenSelector;
defaults: TokenStyleDefaults;
}
export interface TokenStylingRule {
match(classification: TokenClassification): number;
value: TokenStyle;
selector: TokenClassification;
style: TokenStyle;
selector: TokenSelector;
}
/**
@@ -124,33 +127,39 @@ export interface ITokenClassificationRegistry {
/**
* Register a token type to the registry.
* @param id The TokenType id as used in theme description files
* @description the description
* @param 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
* @param description the description
*/
registerTokenModifier(id: string, description: string): void;
getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined;
getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule;
/**
* Parses a token selector from a selector string.
* @param selectorString selector string in the form (*|type)(.modifier)*
* @returns the parsesd selector
* @throws an error if the string is not a valid selector
*/
parseTokenSelector(selectorString: string): TokenSelector;
/**
* Register a TokenStyle default to the registry.
* @param selector The rule selector
* @param defaults The default values
*/
registerTokenStyleDefault(selector: TokenClassification, defaults: TokenStyleDefaults): void;
registerTokenStyleDefault(selector: TokenSelector, defaults: TokenStyleDefaults): void;
/**
* Deregister a TokenStyle default to the registry.
* @param selector The rule selector
*/
deregisterTokenStyleDefault(selector: TokenClassification): void;
deregisterTokenStyleDefault(selector: TokenSelector): void;
/**
* Deregister a TokenType from the registry.
@@ -277,35 +286,40 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
}
private newMatcher(selector: TokenClassification) {
const score = getTokenStylingScore(selector);
return (classification: TokenClassification) => {
const selectorType = selector.type;
if (selectorType !== TOKEN_TYPE_WILDCARD_NUM && selectorType !== classification.type) {
return -1;
}
const selectorModifier = selector.modifiers;
if ((classification.modifiers & selectorModifier) !== selectorModifier) {
return -1;
}
return score;
};
}
public parseTokenSelector(selectorString: string): TokenSelector {
const [type, ...modifiers] = selectorString.split('.');
const selectorClassification = this.getTokenClassification(type, modifiers);
if (!selectorClassification) {
return {
match: () => -1,
asString: () => selectorString
};
}
const score = getTokenStylingScore(selectorClassification);
public getTokenStylingRule(selector: TokenClassification, value: TokenStyle): TokenStylingRule {
return {
match: this.newMatcher(selector),
value,
selector
match: (classification: TokenClassification) => {
if (selectorClassification.type !== TOKEN_TYPE_WILDCARD_NUM && selectorClassification.type !== classification.type) {
return -1;
}
const selectorModifier = selectorClassification.modifiers;
if ((classification.modifiers & selectorModifier) !== selectorModifier) {
return -1;
}
return score;
},
asString: () => selectorString
};
}
public registerTokenStyleDefault(selector: TokenClassification, defaults: TokenStyleDefaults): void {
this.tokenStylingDefaultRules.push({ selector, match: this.newMatcher(selector), defaults });
public registerTokenStyleDefault(selector: TokenSelector, defaults: TokenStyleDefaults): void {
this.tokenStylingDefaultRules.push({ selector, defaults });
}
public deregisterTokenStyleDefault(classification: TokenClassification): void {
this.tokenStylingDefaultRules = this.tokenStylingDefaultRules.filter(r => !(r.selector.type === classification.type && r.selector.modifiers === classification.modifiers));
public deregisterTokenStyleDefault(selector: TokenSelector): void {
const selectorString = selector.asString();
this.tokenStylingDefaultRules = this.tokenStylingDefaultRules.filter(r => !(r.selector.asString() === selectorString));
}
public deregisterTokenType(id: string): void {
@@ -361,8 +375,12 @@ function registerDefaultClassifications(): void {
tokenClassificationRegistry.registerTokenType(id, description, deprecationMessage);
if (scopesToProbe || extendsTC) {
const classification = tokenClassificationRegistry.getTokenClassification(id, []);
tokenClassificationRegistry.registerTokenStyleDefault(classification!, { scopesToProbe, light: extendsTC, dark: extendsTC, hc: extendsTC });
try {
const selector = tokenClassificationRegistry.parseTokenSelector(id);
tokenClassificationRegistry.registerTokenStyleDefault(selector, { scopesToProbe, light: extendsTC, dark: extendsTC, hc: extendsTC });
} catch (e) {
console.log(e);
}
}
return id;
}
@@ -27,7 +27,7 @@ import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/s
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition } from 'vs/workbench/services/themes/common/colorThemeData';
import { TokenStylingRule, TokenStyleData } from 'vs/platform/theme/common/tokenClassificationRegistry';
import { TokenStylingRule, TokenStyleData, TokenStyle } from 'vs/platform/theme/common/tokenClassificationRegistry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export interface IEditorSemanticHighlightingOptions {
@@ -546,9 +546,9 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
} else if (isTokenStylingRule(definition)) {
const scope = theme.getTokenStylingRuleScope(definition);
if (scope === 'setting') {
return `User settings`; // todo: print selector and style once selector is a string
return `User settings: ${definition.selector.asString()} - ${this._renderStyleProperty(definition.style, property)}`;
} else if (scope === 'theme') {
return `Color theme`; // todo: print selector and style once selector is a string
return `Color theme: ${definition.selector.asString()} - ${this._renderStyleProperty(definition.style, property)}`;
}
return '';
} else if (typeof definition === 'string') {
@@ -560,7 +560,14 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
}
return '';
} else {
return String(definition[property]);
return this._renderStyleProperty(definition, property);
}
}
private _renderStyleProperty(style: TokenStyle, property: keyof TokenStyleData) {
switch (property) {
case 'foreground': return style.foreground ? Color.Format.CSS.formatHexA(style.foreground, true) : '';
default: return style[property] !== undefined ? String(style[property]) : '';
}
}
@@ -158,7 +158,7 @@ export class ColorThemeData implements IColorTheme {
}
if (this.tokenStylingRules === undefined) {
for (const rule of tokenClassificationRegistry.getTokenStylingDefaultRules()) {
const matchScore = rule.match(classification);
const matchScore = rule.selector.match(classification);
if (matchScore >= 0) {
let style: TokenStyle | undefined;
if (rule.defaults.scopesToProbe) {
@@ -178,16 +178,16 @@ export class ColorThemeData implements IColorTheme {
}
} else {
for (const rule of this.tokenStylingRules) {
const matchScore = rule.match(classification);
const matchScore = rule.selector.match(classification);
if (matchScore >= 0) {
_processStyle(matchScore, rule.value, rule);
_processStyle(matchScore, rule.style, rule);
}
}
}
for (const rule of this.customTokenStylingRules) {
const matchScore = rule.match(classification);
const matchScore = rule.selector.match(classification);
if (matchScore >= 0) {
_processStyle(matchScore, rule.value, rule);
_processStyle(matchScore, rule.style, rule);
}
}
return TokenStyle.fromData(result);
@@ -222,7 +222,7 @@ export class ColorThemeData implements IColorTheme {
});
if (this.tokenStylingRules) {
this.tokenStylingRules.forEach(r => index.add(r.value.foreground));
this.tokenStylingRules.forEach(r => index.add(r.style.foreground));
} else {
tokenClassificationRegistry.getTokenStylingDefaultRules().forEach(r => {
const defaultColor = r.defaults[this.type];
@@ -231,7 +231,7 @@ export class ColorThemeData implements IColorTheme {
}
});
}
this.customTokenStylingRules.forEach(r => index.add(r.value.foreground));
this.customTokenStylingRules.forEach(r => index.add(r.style.foreground));
this.tokenColorIndex = index;
}
@@ -704,9 +704,8 @@ function getScopeMatcher(rule: ITextMateThemingRule): Matcher<ProbeScope> {
function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenStyleCustomizations, result: TokenStylingRule[] = []) {
for (let key in tokenStylingRuleSection) {
if (key[0] !== '[') {
const [type, ...modifiers] = key.split('.');
const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers);
if (classification) {
try {
const selector = tokenClassificationRegistry.parseTokenSelector(key);
const settings = tokenStylingRuleSection[key];
let style: TokenStyle | undefined;
if (typeof settings === 'string') {
@@ -715,8 +714,10 @@ function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenSt
style = TokenStyle.fromSettings(settings.foreground, settings.fontStyle);
}
if (style) {
result.push(tokenClassificationRegistry.getTokenStylingRule(classification, style));
result.push({ selector, style });
}
} catch (e) {
// invalid selector, ignore
}
}
}
@@ -237,20 +237,23 @@ export class TokenClassificationExtensionPoints {
tokenStyleDefault.dark = validateStyle(contribution.dark, 'semanticTokenStyleDefaults.dark', collector);
tokenStyleDefault.hc = validateStyle(contribution.highContrast, 'semanticTokenStyleDefaults.highContrast', collector);
const [type, ...modifiers] = contribution.selector.split('.');
const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers);
if (classification) {
tokenClassificationRegistry.registerTokenStyleDefault(classification, tokenStyleDefault);
try {
const selector = tokenClassificationRegistry.parseTokenSelector(contribution.selector);
tokenClassificationRegistry.registerTokenStyleDefault(selector, tokenStyleDefault);
} catch (e) {
collector.error(nls.localize('invalid.selector.parsing', "configuration.semanticTokenStyleDefaults.selector': Problems parsing {0}.", contribution.selector));
// invalid selector, ignore
}
}
}
for (const extension of delta.removed) {
const extensionValue = <ITokenStyleDefaultExtensionPoint[]>extension.value;
for (const contribution of extensionValue) {
const [type, ...modifiers] = contribution.selector.split('.');
const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers);
if (classification) {
tokenClassificationRegistry.deregisterTokenStyleDefault(classification);
try {
const selector = tokenClassificationRegistry.parseTokenSelector(contribution.selector);
tokenClassificationRegistry.deregisterTokenStyleDefault(selector);
} catch (e) {
// invalid selector, ignore
}
}
}