language specific scopes

This commit is contained in:
Martin Aeschlimann 2020-03-29 18:03:08 +02:00
parent 3ddb3d0bd6
commit 3c938c800a
8 changed files with 177 additions and 177 deletions

View file

@ -90,6 +90,30 @@
"path": "./syntaxes/Regular Expressions (JavaScript).tmLanguage" "path": "./syntaxes/Regular Expressions (JavaScript).tmLanguage"
} }
], ],
"semanticTokenScopes": [
{
"language": "javascript",
"scopes": {
"property": ["variable.other.property.js"],
"property.readonly": ["variable.other.constant.property.js"],
"variable": ["variable.other.readwrite.js"],
"variable.readonly": ["variable.other.constant.object.js"],
"function": ["entity.name.function.js"],
"namespace": ["entity.name.type.module.js"]
}
},
{
"language": "javascriptreact",
"scopes": {
"property": ["variable.other.property.jsx"],
"property.readonly": ["variable.other.constant.property.jsx"],
"variable": ["variable.other.readwrite.jsx"],
"variable.readonly": ["variable.other.constant.object.jsx"],
"function": ["entity.name.function.jsx"],
"namespace": ["entity.name.type.module.jsx"]
}
}
],
"snippets": [ "snippets": [
{ {
"language": "javascript", "language": "javascript",

View file

@ -79,6 +79,30 @@
} }
} }
], ],
"semanticTokenScopes": [
{
"language": "typescript",
"scopes": {
"property": ["variable.other.property.ts"],
"property.readonly": ["variable.other.constant.property.ts"],
"variable": ["variable.other.readwrite.ts"],
"variable.readonly": ["variable.other.constant.object.ts"],
"function": ["entity.name.function.ts"],
"namespace": ["entity.name.type.module.ts"]
}
},
{
"language": "typescriptreact",
"scopes": {
"property": ["variable.other.property.tsx"],
"property.readonly": ["variable.other.constant.property.tsx"],
"variable": ["variable.other.readwrite.tsx"],
"variable.readonly": ["variable.other.constant.object.tsx"],
"function": ["entity.name.function.tsx"],
"namespace": ["entity.name.type.module.tsx"]
}
}
],
"snippets": [ "snippets": [
{ {
"language": "typescript", "language": "typescript",

View file

@ -1,68 +1,59 @@
{ {
"name": "vscode-colorize-tests", "name": "vscode-colorize-tests",
"description": "Colorize tests for VS Code", "description": "Colorize tests for VS Code",
"version": "0.0.1", "version": "0.0.1",
"publisher": "vscode", "publisher": "vscode",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"activationEvents": [ "activationEvents": [
"onLanguage:json" "onLanguage:json"
], ],
"main": "./out/colorizerTestMain", "main": "./out/colorizerTestMain",
"enableProposedApi": true, "enableProposedApi": true,
"engines": { "engines": {
"vscode": "*" "vscode": "*"
}, },
"scripts": { "scripts": {
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json" "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json"
}, },
"dependencies": { "dependencies": {
"jsonc-parser": "2.2.1" "jsonc-parser": "2.2.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.11.7", "@types/node": "^12.11.7",
"mocha-junit-reporter": "^1.17.0", "mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7", "mocha-multi-reporters": "^1.1.7",
"vscode": "1.1.5" "vscode": "1.1.5"
}, },
"contributes": { "contributes": {
"semanticTokenTypes": [ "semanticTokenTypes": [
{ {
"id": "testToken", "id": "testToken",
"description": "A test token" "description": "A test token"
} }
], ],
"semanticTokenModifiers": [ "semanticTokenModifiers": [
{ {
"id": "testModifier", "id": "testModifier",
"description": "A test modifier" "description": "A test modifier"
} }
], ],
"semanticTokenStyleDefaults": [ "semanticTokenScopes": [
{ {
"selector": "testToken", "scopes": {
"scope": [ "entity.name.function.special" ] "testToken": [
}, "entity.name.function.special"
{ ]
"selector": "*.testModifier", }
"light": { }
"fontStyle": "bold" ],
}, "productIconThemes": [
"dark": { {
"fontStyle": "bold" "id": "Test Product Icons",
}, "label": "The Test Product Icon Theme",
"highContrast": { "path": "./producticons/test-product-icon-theme.json",
"fontStyle": "bold" "_watch": true
} }
} ]
], }
"productIconThemes": [
{
"id": "Test Product Icons",
"label": "The Test Product Icon Theme",
"path": "./producticons/test-product-icon-theme.json",
"_watch": true
}
]
}
} }

View file

@ -28,7 +28,7 @@ export const fontStylePattern = '^(\\s*(-?italic|-?bold|-?underline))*\\s*$';
export interface TokenSelector { export interface TokenSelector {
match(type: string, modifiers: string[], language: string): number; match(type: string, modifiers: string[], language: string): number;
readonly selectorString: string; readonly id: string;
} }
export interface TokenTypeOrModifierContribution { export interface TokenTypeOrModifierContribution {
@ -155,7 +155,7 @@ export namespace TokenStylingRule {
} }
export function toJSONObject(rule: TokenStylingRule): any { export function toJSONObject(rule: TokenStylingRule): any {
return { return {
_selector: rule.selector.selectorString, _selector: rule.selector.id,
_style: TokenStyle.toJSONObject(rule.style) _style: TokenStyle.toJSONObject(rule.style)
}; };
} }
@ -164,7 +164,7 @@ export namespace TokenStylingRule {
return true; return true;
} }
return r1 !== undefined && r2 !== undefined return r1 !== undefined && r2 !== undefined
&& r1.selector && r2.selector && r1.selector.selectorString === r2.selector.selectorString && r1.selector && r2.selector && r1.selector.id === r2.selector.id
&& TokenStyle.equals(r1.style, r2.style); && TokenStyle.equals(r1.style, r2.style);
} }
export function is(r: any): r is TokenStylingRule { export function is(r: any): r is TokenStylingRule {
@ -203,10 +203,11 @@ export interface ITokenClassificationRegistry {
/** /**
* Parses a token selector from a selector string. * Parses a token selector from a selector string.
* @param selectorString selector string in the form (*|type)(.modifier)* * @param selectorString selector string in the form (*|type)(.modifier)*
* @param language language to which the selector applies or undefined if the selector is for all languafe
* @returns the parsesd selector * @returns the parsesd selector
* @throws an error if the string is not a valid selector * @throws an error if the string is not a valid selector
*/ */
parseTokenSelector(selectorString: string): TokenSelector; parseTokenSelector(selectorString: string, language?: string): TokenSelector;
/** /**
* Register a TokenStyle default to the registry. * Register a TokenStyle default to the registry.
@ -335,13 +336,13 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
this.tokenStylingSchema.properties[`*.${id}`] = getStylingSchemeEntry(description, deprecationMessage); this.tokenStylingSchema.properties[`*.${id}`] = getStylingSchemeEntry(description, deprecationMessage);
} }
public parseTokenSelector(selectorString: string): TokenSelector { public parseTokenSelector(selectorString: string, language?: string): TokenSelector {
const selector = parseClassifierString(selectorString); const selector = parseClassifierString(selectorString, language);
if (!selector.type) { if (!selector.type) {
return { return {
match: () => -1, match: () => -1,
selectorString id: '$invalid'
}; };
} }
@ -370,7 +371,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
} }
return score + selector.modifiers.length * 100; return score + selector.modifiers.length * 100;
}, },
selectorString id: `${[selector.type, ...selector.modifiers.sort()].join('.')}${selector.language !== undefined ? ':' + selector.language : ''}`
}; };
} }
@ -379,8 +380,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
} }
public deregisterTokenStyleDefault(selector: TokenSelector): void { public deregisterTokenStyleDefault(selector: TokenSelector): void {
const selectorString = selector.selectorString; const selectorString = selector.id;
this.tokenStylingDefaultRules = this.tokenStylingDefaultRules.filter(r => r.selector.selectorString !== selectorString); this.tokenStylingDefaultRules = this.tokenStylingDefaultRules.filter(r => r.selector.id !== selectorString);
} }
public deregisterTokenType(id: string): void { public deregisterTokenType(id: string): void {
@ -442,9 +443,11 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
const CHAR_LANGUAGE = TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR.charCodeAt(0); const CHAR_LANGUAGE = TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR.charCodeAt(0);
const CHAR_MODIFIER = CLASSIFIER_MODIFIER_SEPARATOR.charCodeAt(0); const CHAR_MODIFIER = CLASSIFIER_MODIFIER_SEPARATOR.charCodeAt(0);
export function parseClassifierString(s: string): { type: string, modifiers: string[], language: string | undefined; } { export function parseClassifierString(s: string, defaultLanguage: string): { type: string, modifiers: string[], language: string; };
export function parseClassifierString(s: string, defaultLanguage?: string): { type: string, modifiers: string[], language: string | undefined; };
export function parseClassifierString(s: string, defaultLanguage: string | undefined): { type: string, modifiers: string[], language: string | undefined; } {
let k = s.length; let k = s.length;
let language: string | undefined = undefined; let language: string | undefined = defaultLanguage;
const modifiers = []; const modifiers = [];
for (let i = k - 1; i >= 0; i--) { for (let i = k - 1; i >= 0; i--) {

View file

@ -551,9 +551,9 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
} else if (TokenStylingRule.is(definition)) { } else if (TokenStylingRule.is(definition)) {
const scope = theme.getTokenStylingRuleScope(definition); const scope = theme.getTokenStylingRuleScope(definition);
if (scope === 'setting') { if (scope === 'setting') {
return `User settings: ${definition.selector.selectorString} - ${this._renderStyleProperty(definition.style, property)}`; return `User settings: ${definition.selector.id} - ${this._renderStyleProperty(definition.style, property)}`;
} else if (scope === 'theme') { } else if (scope === 'theme') {
return `Color theme: ${definition.selector.selectorString} - ${this._renderStyleProperty(definition.style, property)}`; return `Color theme: ${definition.selector.id} - ${this._renderStyleProperty(definition.style, property)}`;
} }
return ''; return '';
} else { } else {

View file

@ -212,8 +212,8 @@ export class ColorThemeData implements IWorkbenchColorTheme {
if (tokenStyleValue === undefined) { if (tokenStyleValue === undefined) {
return undefined; return undefined;
} else if (typeof tokenStyleValue === 'string') { } else if (typeof tokenStyleValue === 'string') {
const { type, modifiers, language } = parseClassifierString(tokenStyleValue); const { type, modifiers, language } = parseClassifierString(tokenStyleValue, '');
return this.getTokenStyle(type, modifiers, language || ''); return this.getTokenStyle(type, modifiers, language);
} else if (typeof tokenStyleValue === 'object') { } else if (typeof tokenStyleValue === 'object') {
return tokenStyleValue; return tokenStyleValue;
} }
@ -248,8 +248,8 @@ export class ColorThemeData implements IWorkbenchColorTheme {
} }
public getTokenStyleMetadata(typeWithLanguage: string, modifiers: string[], defaultLanguage: string, useDefault = true, definitions: TokenStyleDefinitions = {}): ITokenStyle | undefined { public getTokenStyleMetadata(typeWithLanguage: string, modifiers: string[], defaultLanguage: string, useDefault = true, definitions: TokenStyleDefinitions = {}): ITokenStyle | undefined {
const { type, language } = parseClassifierString(typeWithLanguage); const { type, language } = parseClassifierString(typeWithLanguage, defaultLanguage);
let style = this.getTokenStyle(type, modifiers, language || defaultLanguage, useDefault, definitions); let style = this.getTokenStyle(type, modifiers, language, useDefault, definitions);
if (!style) { if (!style) {
return undefined; return undefined;
} }

View file

@ -5,8 +5,7 @@
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { getTokenClassificationRegistry, ITokenClassificationRegistry, typeAndModifierIdPattern, TokenStyleDefaults, TokenStyle, fontStylePattern, selectorPattern } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { getTokenClassificationRegistry, ITokenClassificationRegistry, typeAndModifierIdPattern } from 'vs/platform/theme/common/tokenClassificationRegistry';
import { textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema';
interface ITokenTypeExtensionPoint { interface ITokenTypeExtensionPoint {
id: string; id: string;
@ -20,24 +19,10 @@ interface ITokenModifierExtensionPoint {
} }
interface ITokenStyleDefaultExtensionPoint { interface ITokenStyleDefaultExtensionPoint {
selector: string; language?: string;
scope?: string[]; scopes: { [selector: string]: string[] };
light?: {
foreground?: string;
fontStyle?: string;
};
dark?: {
foreground?: string;
fontStyle?: string;
};
highContrast?: {
foreground?: string;
fontStyle?: string;
};
} }
const colorPattern = '^#([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$';
const tokenClassificationRegistry: ITokenClassificationRegistry = getTokenClassificationRegistry(); const tokenClassificationRegistry: ITokenClassificationRegistry = getTokenClassificationRegistry();
const tokenTypeExtPoint = ExtensionsRegistry.registerExtensionPoint<ITokenTypeExtensionPoint[]>({ const tokenTypeExtPoint = ExtensionsRegistry.registerExtensionPoint<ITokenTypeExtensionPoint[]>({
@ -86,37 +71,26 @@ const tokenModifierExtPoint = ExtensionsRegistry.registerExtensionPoint<ITokenMo
}); });
const tokenStyleDefaultsExtPoint = ExtensionsRegistry.registerExtensionPoint<ITokenStyleDefaultExtensionPoint[]>({ const tokenStyleDefaultsExtPoint = ExtensionsRegistry.registerExtensionPoint<ITokenStyleDefaultExtensionPoint[]>({
extensionPoint: 'semanticTokenStyleDefaults', extensionPoint: 'semanticTokenScopes',
jsonSchema: { jsonSchema: {
description: nls.localize('contributes.semanticTokenStyleDefaults', 'Contributes semantic token style defaults.'), description: nls.localize('contributes.semanticTokenScopes', 'Contributes semantic token scope maps.'),
type: 'array', type: 'array',
items: { items: {
type: 'object', type: 'object',
properties: { properties: {
selector: { language: {
type: 'string', description: nls.localize('contributes.semanticTokenScopes.languages', 'Lists the languge for which the defaults are.'),
description: nls.localize('contributes.semanticTokenStyleDefaults.selector', 'The selector matching token types and modifiers.'), type: 'string'
pattern: selectorPattern,
patternErrorMessage: nls.localize('contributes.semanticTokenStyleDefaults.selector.format', 'Selectors should be in the form (type|*)(.modifier)*(:language)?'),
}, },
scope: { scopes: {
type: 'array', description: nls.localize('contributes.semanticTokenScopes.scopes', 'Maps a semantic token (described by semantic token selector) to one or more textMate scopes used to represent that token.'),
description: nls.localize('contributes.semanticTokenStyleDefaults.scope', 'A TextMate scope against the current color theme is matched to find the style for the given selector'), type: 'object',
items: { additionalProperties: {
type: 'string' type: 'array',
items: {
type: 'string'
}
} }
},
light: {
description: nls.localize('contributes.semanticTokenStyleDefaults.light', 'The default style used for light themes'),
$ref: textmateColorSettingsSchemaId
},
dark: {
description: nls.localize('contributes.semanticTokenStyleDefaults.dark', 'The default style used for dark themes'),
$ref: textmateColorSettingsSchemaId
},
highContrast: {
description: nls.localize('contributes.semanticTokenStyleDefaults.hc', 'The default style used for high contrast themes'),
$ref: textmateColorSettingsSchemaId
} }
} }
} }
@ -147,24 +121,6 @@ export class TokenClassificationExtensionPoints {
} }
return true; return true;
} }
function validateStyle(style: { foreground?: string; fontStyle?: string; } | undefined, extensionPoint: string, collector: ExtensionMessageCollector): TokenStyle | undefined {
if (!style) {
return undefined;
}
if (style.foreground) {
if (typeof style.foreground !== 'string' || !style.foreground.match(colorPattern)) {
collector.error(nls.localize('invalid.color', "'configuration.{0}.foreground' must follow the pattern #RRGGBB[AA]", extensionPoint));
return undefined;
}
}
if (style.fontStyle) {
if (typeof style.fontStyle !== 'string' || !style.fontStyle.match(fontStylePattern)) {
collector.error(nls.localize('invalid.fontStyle', "'configuration.{0}.fontStyle' must be one or a combination of \'italic\', \'bold\' or \'underline\' or the empty string", extensionPoint));
return undefined;
}
}
return TokenStyle.fromSettings(style.foreground, style.fontStyle);
}
tokenTypeExtPoint.setHandler((extensions, delta) => { tokenTypeExtPoint.setHandler((extensions, delta) => {
for (const extension of delta.added) { for (const extension of delta.added) {
@ -216,49 +172,45 @@ export class TokenClassificationExtensionPoints {
const collector = extension.collector; const collector = extension.collector;
if (!extensionValue || !Array.isArray(extensionValue)) { if (!extensionValue || !Array.isArray(extensionValue)) {
collector.error(nls.localize('invalid.semanticTokenStyleDefaultConfiguration', "'configuration.semanticTokenStyleDefaults' must be an array")); collector.error(nls.localize('invalid.semanticTokenScopes.configuration', "'configuration.semanticTokenScopes' must be an array"));
return; return;
} }
for (const contribution of extensionValue) { for (const contribution of extensionValue) {
if (typeof contribution.selector !== 'string' || contribution.selector.length === 0) { if (contribution.language && typeof contribution.language !== 'string') {
collector.error(nls.localize('invalid.selector', "'configuration.semanticTokenStyleDefaults.selector' must be defined and can not be empty")); collector.error(nls.localize('invalid.semanticTokenScopes.language', "'configuration.semanticTokenScopes.language' must be a string"));
continue; continue;
} }
if (!contribution.selector.match(selectorPattern)) { if (!contribution.scopes || typeof contribution.scopes !== 'object') {
collector.error(nls.localize('invalid.selector.format', "'configuration.semanticTokenStyleDefaults.selector' must be in the form (type|*)(.modifier)*(:language)?")); collector.error(nls.localize('invalid.semanticTokenScopes.scopes', "'configuration.semanticTokenScopes.scopes' must be defined as an object"));
continue; continue;
} }
for (let selectorString in contribution.scopes) {
const tokenStyleDefault: TokenStyleDefaults = {}; const tmScopes = contribution.scopes[selectorString];
if (!Array.isArray(tmScopes) || tmScopes.some(l => typeof l !== 'string')) {
if (contribution.scope) { collector.error(nls.localize('invalid.semanticTokenScopes.scopes.value', "'configuration.semanticTokenScopes.scopes' values must be an array of strings"));
if ((!Array.isArray(contribution.scope) || contribution.scope.some(s => typeof s !== 'string'))) {
collector.error(nls.localize('invalid.scope', "If defined, 'configuration.semanticTokenStyleDefaults.scope' must be an array of strings"));
continue; continue;
} }
tokenStyleDefault.scopesToProbe = [contribution.scope]; try {
} const selector = tokenClassificationRegistry.parseTokenSelector(selectorString, contribution.language);
tokenStyleDefault.light = validateStyle(contribution.light, 'semanticTokenStyleDefaults.light', collector); tokenClassificationRegistry.registerTokenStyleDefault(selector, { scopesToProbe: tmScopes.map(s => s.split(' ')) });
tokenStyleDefault.dark = validateStyle(contribution.dark, 'semanticTokenStyleDefaults.dark', collector); } catch (e) {
tokenStyleDefault.hc = validateStyle(contribution.highContrast, 'semanticTokenStyleDefaults.highContrast', collector); collector.error(nls.localize('invalid.semanticTokenScopes.scopes.selector', "configuration.semanticTokenScopes.scopes': Problems parsing selector {0}.", selectorString));
// invalid selector, ignore
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) { for (const extension of delta.removed) {
const extensionValue = <ITokenStyleDefaultExtensionPoint[]>extension.value; const extensionValue = <ITokenStyleDefaultExtensionPoint[]>extension.value;
for (const contribution of extensionValue) { for (const contribution of extensionValue) {
try { for (let selectorString in contribution.scopes) {
const selector = tokenClassificationRegistry.parseTokenSelector(contribution.selector); const tmScopes = contribution.scopes[selectorString];
tokenClassificationRegistry.deregisterTokenStyleDefault(selector); try {
} catch (e) { const selector = tokenClassificationRegistry.parseTokenSelector(selectorString, contribution.language);
// invalid selector, ignore tokenClassificationRegistry.registerTokenStyleDefault(selector, { scopesToProbe: tmScopes.map(s => s.split(' ')) });
} catch (e) {
// invalid selector, ignore
}
} }
} }
} }

View file

@ -398,8 +398,11 @@ suite('Themes - TokenStyleResolving', () => {
test('language - scope resolving', async () => { test('language - scope resolving', async () => {
const registry = getTokenClassificationRegistry(); const registry = getTokenClassificationRegistry();
registry.registerTokenStyleDefault(registry.parseTokenSelector('type:typescript'), { scopesToProbe: [['entity.name.type.ts']] });
const numberOfDefaultRules = registry.getTokenStylingDefaultRules().length;
registry.registerTokenStyleDefault(registry.parseTokenSelector('type', 'typescript1'), { scopesToProbe: [['entity.name.type.ts1']] });
registry.registerTokenStyleDefault(registry.parseTokenSelector('type:javascript1'), { scopesToProbe: [['entity.name.type.js1']] });
try { try {
const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test');
@ -411,17 +414,20 @@ suite('Themes - TokenStyleResolving', () => {
settings: { foreground: '#aa0000' } settings: { foreground: '#aa0000' }
}, },
{ {
scope: 'entity.name.type.ts', scope: 'entity.name.type.ts1',
settings: { foreground: '#bb0000' } settings: { foreground: '#bb0000' }
} }
] ]
}); });
assertTokenStyles(themeData, { 'type': ts('#aa0000', undefined) }, 'javascript'); assertTokenStyles(themeData, { 'type': ts('#aa0000', undefined) }, 'javascript1');
assertTokenStyles(themeData, { 'type': ts('#bb0000', undefined) }, 'typescript'); assertTokenStyles(themeData, { 'type': ts('#bb0000', undefined) }, 'typescript1');
} finally { } finally {
registry.deregisterTokenType('type/typescript'); registry.deregisterTokenStyleDefault(registry.parseTokenSelector('type', 'typescript1'));
registry.deregisterTokenStyleDefault(registry.parseTokenSelector('type:javascript1'));
assert.equal(registry.getTokenStylingDefaultRules().length, numberOfDefaultRules);
} }
}); });
}); });