From 100318a63e2af38b5a442362d25742d732fc0d94 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 11 Nov 2019 16:04:09 +0100 Subject: [PATCH] styling in settings and themes --- .../common/tokenClassificationRegistry.ts | 182 +++++++++++++----- .../browser/abstractTextMateService.ts | 4 +- .../themes/browser/workbenchThemeService.ts | 24 ++- .../services/themes/common/colorThemeData.ts | 128 ++++++++---- .../themes/common/themeCompatibility.ts | 8 +- .../themes/common/workbenchThemeService.ts | 17 +- .../tokenStyleResolving.test.ts | 52 +++-- 7 files changed, 287 insertions(+), 128 deletions(-) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 7817b7a5778..2ec489ae46f 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -7,6 +7,10 @@ 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 @@ -89,6 +93,8 @@ export const Extensions = { export interface ITokenClassificationRegistry { + readonly onDidChangeSchema: Event; + /** * Register a token type to the registry. * @param id The TokenType id as used in theme description files @@ -106,7 +112,7 @@ export interface ITokenClassificationRegistry { getTokenClassificationFromString(str: TokenClassificationString): TokenClassification | undefined; getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined; - getTokenStylingRule(classification: TokenClassification | string | undefined, value: TokenStyle): TokenStylingRule | undefined; + getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule; /** * Register a TokenStyle default to the registry. @@ -138,13 +144,19 @@ export interface ITokenClassificationRegistry { /** * Resolves a token classification against the given rules and default rules from the registry. */ - resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[], useDefault: boolean, theme: ITheme): TokenStyle | undefined; -} - + 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(); + readonly onDidChangeSchema: Event = this._onDidChangeSchema.event; + private currentTypeNumber = 0; private currentModifierBit = 1; @@ -153,6 +165,38 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { 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 = {}; @@ -164,6 +208,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { 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 { @@ -171,6 +217,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { 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 { @@ -197,14 +245,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return undefined; } - public getTokenStylingRule(classification: TokenClassification | string | undefined, value: TokenStyle): TokenStylingRule | undefined { - if (typeof classification === 'string') { - classification = this.getTokenClassificationFromString(classification); - } - if (classification) { - return { classification, matchScore: getTokenStylingScore(classification), value }; - } - return undefined; + public getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule { + return { classification, matchScore: getTokenStylingScore(classification), value }; } public registerTokenStyleDefault(classification: TokenClassification, defaults: TokenStyleDefaults): void { @@ -213,10 +255,12 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { 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[] { @@ -227,7 +271,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return Object.keys(this.tokenModifierById).map(id => this.tokenModifierById[id]); } - public resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[], useDefault: boolean, theme: ITheme): TokenStyle | undefined { + public resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[] | undefined, customThemingRules: TokenStylingRule[], theme: ITheme): TokenStyle | undefined { let result: any = { foreground: undefined, bold: undefined, @@ -257,7 +301,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } } } - if (useDefault) { + if (themingRules === undefined) { for (const rule of this.tokenStylingDefaultRules) { const matchScore = match(rule, classification); if (matchScore >= 0) { @@ -270,8 +314,15 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } } } + } else { + for (const rule of themingRules) { + const matchScore = match(rule, classification); + if (matchScore >= 0) { + _processStyle(matchScore, rule.value); + } + } } - for (const rule of themingRules) { + for (const rule of customThemingRules) { const matchScore = match(rule, classification); if (matchScore >= 0) { _processStyle(matchScore, rule.value); @@ -297,6 +348,10 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return undefined; } + public getTokenStylingSchema(): IJSONSchema { + return this.tokenStylingSchema; + } + public toString() { let sorter = (a: string, b: string) => { @@ -348,40 +403,40 @@ export function getTokenClassificationRegistry(): ITokenClassificationRegistry { return tokenClassificationRegistry; } -export const comments = registerTokenType('comments', nls.localize('comments', "Token style for comments."), [['comment']]); -export const strings = registerTokenType('strings', nls.localize('strings', "Token style for strings."), [['string']]); -export const keywords = registerTokenType('keywords', nls.localize('keywords', "Token style for keywords."), [['keyword.control']]); -export const numbers = registerTokenType('numbers', nls.localize('numbers', "Token style for numbers."), [['constant.numeric']]); -export const regexp = registerTokenType('regexp', nls.localize('regexp', "Token style for regular expressions."), [['constant.regexp']]); -export const operators = registerTokenType('operators', nls.localize('operator', "Token style for operators."), [['keyword.operator']]); - -export const namespaces = registerTokenType('namespaces', nls.localize('namespace', "Token style for namespaces."), [['entity.name.namespace']]); - -export const types = registerTokenType('types', nls.localize('types', "Token style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); -export const structs = registerTokenType('structs', nls.localize('struct', "Token style for struct."), [['storage.type.struct']], types); -export const classes = registerTokenType('classes', nls.localize('class', "Token style for classes."), [['ntity.name.class']], types); -export const interfaces = registerTokenType('interfaces', nls.localize('interface', "Token style for interfaces."), undefined, types); -export const enums = registerTokenType('enums', nls.localize('enum', "Token style for enums."), undefined, types); -export const parameterTypes = registerTokenType('parameterTypes', nls.localize('parameterType', "Token style for parameterTypes."), undefined, types); - -export const functions = registerTokenType('functions', nls.localize('functions', "Token style for functions."), [['entity.name.function'], ['support.function']]); -export const macros = registerTokenType('macros', nls.localize('macro', "Token style for macros."), undefined, functions); - -export const variables = registerTokenType('variables', nls.localize('variables', "Token style for variables."), [['variable'], ['entity.name.variable']]); -export const constants = registerTokenType('constants', nls.localize('constants', "Token style for constants."), undefined, variables); -export const parameters = registerTokenType('parameters', nls.localize('parameters', "Token style for parameters."), undefined, variables); -export const property = registerTokenType('properties', nls.localize('properties', "Token style for properties."), undefined, variables); - -export const labels = registerTokenType('labels', nls.localize('labels', "Token style for labels."), undefined); - -export const m_declaration = registerTokenModifier('declaration', nls.localize('declaration', "Token modifier for declarations."), undefined); -export const m_documentation = registerTokenModifier('documentation', nls.localize('documentation', "Token modifier for documentation."), undefined); -export const m_member = registerTokenModifier('member', nls.localize('member', "Token modifier for member."), undefined); -export const m_static = registerTokenModifier('static', nls.localize('static', "Token modifier for statics."), undefined); -export const m_abstract = registerTokenModifier('abstract', nls.localize('abstract', "Token modifier for abstracts."), undefined); -export const m_deprecated = registerTokenModifier('deprecated', nls.localize('deprecated', "Token modifier for deprecated."), undefined); -export const m_modification = registerTokenModifier('modification', nls.localize('modification', "Token modifier for modification."), undefined); -export const m_async = registerTokenModifier('async', nls.localize('async', "Token modifier for async."), undefined); +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/ @@ -392,3 +447,32 @@ function bitCount(u: number) { 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(JSONExtensions.JSONContribution); +schemaRegistry.registerSchema(tokenStylingSchemaId, tokenClassificationRegistry.getTokenStylingSchema()); + +const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(tokenStylingSchemaId), 200); +tokenClassificationRegistry.onDidChangeSchema(() => { + if (!delayer.isScheduled()) { + delayer.schedule(); + } +}); diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index cb553b6fcc8..dc9222e4b02 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -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; } diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 85b09406b2c..91e4c86fe53 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -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(CUSTOM_EDITOR_COLORS_SETTING) || {}; } + private get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { + return this.configurationService.getValue(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); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index e417676fc2e..4a1b760b4e2 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -6,7 +6,7 @@ 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'; @@ -49,12 +49,13 @@ 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[] = []; + private tokenStylingRules: TokenStylingRule[] | undefined = undefined; + private customTokenStylingRules: TokenStylingRule[] = []; private themeTokenScopeMatchers: Matcher[] | undefined; private customTokenScopeMatchers: Matcher[] | undefined; @@ -66,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)!; @@ -81,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; @@ -115,7 +116,7 @@ export class ColorThemeData implements IColorTheme { public getTokenStyle(tokenClassification: TokenClassification, useDefault?: boolean): TokenStyle | undefined { // todo: cache results - return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, this.tokenStylingRules, useDefault !== false, this); + return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, this.tokenStylingRules, this.customTokenStylingRules, this); } public getDefault(colorId: ColorIdentifier): Color | undefined { @@ -123,7 +124,7 @@ export class ColorThemeData implements IColorTheme { } public getDefaultTokenStyle(tokenClassification: TokenClassification): TokenStyle | undefined { - return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, [], true, this); + return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, undefined, [], this); } public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { @@ -136,12 +137,12 @@ export class ColorThemeData implements IColorTheme { } for (let scope of scopes) { - let foreground: string | null = null; - let fontStyle: string | null = null; + let foreground: string | undefined = undefined; + let fontStyle: string | undefined = undefined; let foregroundScore = -1; let fontStyleScore = -1; - function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITokenColorizationRule[]) { + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITextMateThemingRule[]) { for (let i = 0; i < scopeMatchers.length; i++) { const score = scopeMatchers[i](scope); if (score >= 0) { @@ -157,7 +158,7 @@ export class ColorThemeData implements IColorTheme { } findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); - if (foreground !== null || fontStyle !== null) { + if (foreground !== undefined || fontStyle !== undefined) { return getTokenStyle(foreground, fontStyle); } } @@ -200,8 +201,14 @@ export class ColorThemeData implements IColorTheme { } } - public setTokenStyleRules(tokenStylingRules: TokenStylingRule[]) { - this.tokenStylingRules = tokenStylingRules; + 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) { @@ -243,9 +250,17 @@ export class ColorThemeData implements IColorTheme { } this.themeTokenColors = []; this.themeTokenScopeMatchers = undefined; - this.colorMap = {}; - return _loadColorTheme(extensionResourceLoaderService, this.location, this.themeTokenColors, this.colorMap).then(_ => { + + 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; }); } @@ -358,7 +373,7 @@ function toCSSSelector(extensionId: string, path: string) { return str; } -function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { +function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[] | undefined }): Promise { if (resources.extname(themeLocation) === '.json') { return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => { let errors: Json.ParseError[] = []; @@ -370,11 +385,11 @@ function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoade } let includeCompletes: Promise = 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; @@ -386,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 { +function _loadSyntaxTokens(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap }): Promise { 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))); @@ -427,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' } }, @@ -489,7 +508,7 @@ function scopesAreMatching(thisScopeName: string, scopeName: string): boolean { return thisScopeName.length > len && thisScopeName.substr(0, len) === scopeName && thisScopeName[len] === '.'; } -function getScopeMatcher(rule: ITokenColorizationRule): Matcher { +function getScopeMatcher(rule: ITextMateThemingRule): Matcher { const ruleScope = rule.scope; if (!ruleScope || !rule.settings) { return noMatch; @@ -515,17 +534,56 @@ function getScopeMatcher(rule: ITokenColorizationRule): Matcher { }; } -function getTokenStyle(foreground: string | null, fontStyle: string | null): TokenStyle | undefined { +function getTokenStyle(foreground: string | undefined, fontStyle: string | undefined): TokenStyle { let foregroundColor = undefined; - if (foreground !== null) { + if (foreground !== undefined) { foregroundColor = Color.fromHex(foreground); } let bold, underline, italic; - if (fontStyle !== null) { - bold = fontStyle.indexOf('bold') !== -1; - underline = fontStyle.indexOf('underline') !== -1; - italic = fontStyle.indexOf('italic') !== -1; + 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); +} diff --git a/src/vs/workbench/services/themes/common/themeCompatibility.ts b/src/vs/workbench/services/themes/common/themeCompatibility.ts index 6518ff4a4ac..7af65c955ad 100644 --- a/src/vs/workbench/services/themes/common/themeCompatibility.ts +++ b/src/vs/workbench/services/themes/common/themeCompatibility.ts @@ -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; } } } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 8684b54a804..9937ca11be9 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -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 -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index a0fe30b8683..ab41c917cb8 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -6,7 +6,7 @@ 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, TokenStylingRule } from 'vs/platform/theme/common/tokenClassificationRegistry'; +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'; @@ -44,14 +44,6 @@ function tokenStyleAsString(ts: TokenStyle | undefined | null) { return str; } -function getTokenStyleRules(rules: [string, TokenStyle][]): TokenStylingRule[] { - return rules.map(e => { - const rule = tokenClassificationRegistry.getTokenStylingRule(e[0], e[1]); - assert.ok(rule); - return rule!; - }); -} - function assertTokenStyle(actual: TokenStyle | undefined | null, expected: TokenStyle | undefined | null, message?: string) { assert.equal(tokenStyleAsString(actual), tokenStyleAsString(expected), message); } @@ -87,7 +79,7 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyles(themeData, { [comments]: ts('#88846f', undefinedStyle), [variables]: ts('#F8F8F2', unsetStyle), - [types]: ts('#A6E22E', { underline: true, bold: false, italic: false }), + [types]: ts('#A6E22E', { underline: true }), [functions]: ts('#A6E22E', unsetStyle), [strings]: ts('#E6DB74', undefinedStyle), [numbers]: ts('#AE81FF', undefinedStyle), @@ -187,7 +179,7 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyles(themeData, { [comments]: ts('#384887', undefinedStyle), [variables]: ts(undefined, unsetStyle), - [types]: ts('#ffeebb', { underline: true, bold: false, italic: false }), + [types]: ts('#ffeebb', { underline: true }), [functions]: ts('#ddbb88', unsetStyle), [strings]: ts('#22aa44', undefinedStyle), [numbers]: ts('#f280d0', undefinedStyle), @@ -259,43 +251,43 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword.operators'); tokenStyle = themeData.resolveScopes([['storage']]); - assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, underline: false, bold: false }), 'storage'); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true }), 'storage'); tokenStyle = themeData.resolveScopes([['storage.type']]); - assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true, underline: false, bold: false }), 'storage.type'); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type'); tokenStyle = themeData.resolveScopes([['entity.name.class']]); - assertTokenStyle(tokenStyle, ts('#A6E22E', { underline: true, italic: false, bold: false }), '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, underline: false, bold: false }), 'storage.type'); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type'); }); test('rule matching', async () => { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); themeData.setCustomColors({ 'editor.foreground': '#000000' }); - themeData.setTokenStyleRules(getTokenStyleRules([ - ['types', ts('#ff0000', undefined)], - ['classes', ts('#0000ff', { italic: true })], - ['*.static', ts(undefined, { bold: true })], - ['*.declaration', ts(undefined, { italic: true })], - ['*.async.static', ts('#00ffff', { bold: false, underline: true })], - ['*.async', ts('#000fff', { italic: false, underline: true })] - ])); + 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, italic: undefined, underline: undefined }), - 'types.static.declaration': ts('#ff0000', { bold: true, italic: true, underline: undefined }), - 'classes': ts('#0000ff', { bold: undefined, italic: true, underline: undefined }), - 'classes.static.declaration': ts('#0000ff', { bold: true, italic: true, underline: undefined }), - 'classes.declaration': ts('#0000ff', { bold: undefined, italic: true, underline: undefined }), - 'classes.declaration.async': ts('#000fff', { bold: undefined, italic: false, underline: true }), - 'classes.declaration.async.static': ts('#00ffff', { bold: false, italic: false, underline: true }), + '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 }), }); }); -- GitLab