UNPKG

monaco-editor

Version:
285 lines (284 loc) • 10.4 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Color } from '../../../../base/common/color.js'; export class ParsedTokenThemeRule { constructor(token, index, fontStyle, foreground, background) { this._parsedThemeRuleBrand = undefined; this.token = token; this.index = index; this.fontStyle = fontStyle; this.foreground = foreground; this.background = background; } } /** * Parse a raw theme into rules. */ export function parseTokenTheme(source) { if (!source || !Array.isArray(source)) { return []; } const result = []; let resultLen = 0; for (let i = 0, len = source.length; i < len; i++) { const entry = source[i]; let fontStyle = -1 /* FontStyle.NotSet */; if (typeof entry.fontStyle === 'string') { fontStyle = 0 /* FontStyle.None */; const segments = entry.fontStyle.split(' '); for (let j = 0, lenJ = segments.length; j < lenJ; j++) { const segment = segments[j]; switch (segment) { case 'italic': fontStyle = fontStyle | 1 /* FontStyle.Italic */; break; case 'bold': fontStyle = fontStyle | 2 /* FontStyle.Bold */; break; case 'underline': fontStyle = fontStyle | 4 /* FontStyle.Underline */; break; case 'strikethrough': fontStyle = fontStyle | 8 /* FontStyle.Strikethrough */; break; } } } let foreground = null; if (typeof entry.foreground === 'string') { foreground = entry.foreground; } let background = null; if (typeof entry.background === 'string') { background = entry.background; } result[resultLen++] = new ParsedTokenThemeRule(entry.token || '', i, fontStyle, foreground, background); } return result; } /** * Resolve rules (i.e. inheritance). */ function resolveParsedTokenThemeRules(parsedThemeRules, customTokenColors) { // Sort rules lexicographically, and then by index if necessary parsedThemeRules.sort((a, b) => { const r = strcmp(a.token, b.token); if (r !== 0) { return r; } return a.index - b.index; }); // Determine defaults let defaultFontStyle = 0 /* FontStyle.None */; let defaultForeground = '000000'; let defaultBackground = 'ffffff'; while (parsedThemeRules.length >= 1 && parsedThemeRules[0].token === '') { const incomingDefaults = parsedThemeRules.shift(); if (incomingDefaults.fontStyle !== -1 /* FontStyle.NotSet */) { defaultFontStyle = incomingDefaults.fontStyle; } if (incomingDefaults.foreground !== null) { defaultForeground = incomingDefaults.foreground; } if (incomingDefaults.background !== null) { defaultBackground = incomingDefaults.background; } } const colorMap = new ColorMap(); // start with token colors from custom token themes for (const color of customTokenColors) { colorMap.getId(color); } const foregroundColorId = colorMap.getId(defaultForeground); const backgroundColorId = colorMap.getId(defaultBackground); const defaults = new ThemeTrieElementRule(defaultFontStyle, foregroundColorId, backgroundColorId); const root = new ThemeTrieElement(defaults); for (let i = 0, len = parsedThemeRules.length; i < len; i++) { const rule = parsedThemeRules[i]; root.insert(rule.token, rule.fontStyle, colorMap.getId(rule.foreground), colorMap.getId(rule.background)); } return new TokenTheme(colorMap, root); } const colorRegExp = /^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/; export class ColorMap { constructor() { this._lastColorId = 0; this._id2color = []; this._color2id = new Map(); } getId(color) { if (color === null) { return 0; } const match = color.match(colorRegExp); if (!match) { throw new Error('Illegal value for token color: ' + color); } color = match[1].toUpperCase(); let value = this._color2id.get(color); if (value) { return value; } value = ++this._lastColorId; this._color2id.set(color, value); this._id2color[value] = Color.fromHex('#' + color); return value; } getColorMap() { return this._id2color.slice(0); } } export class TokenTheme { static createFromRawTokenTheme(source, customTokenColors) { return this.createFromParsedTokenTheme(parseTokenTheme(source), customTokenColors); } static createFromParsedTokenTheme(source, customTokenColors) { return resolveParsedTokenThemeRules(source, customTokenColors); } constructor(colorMap, root) { this._colorMap = colorMap; this._root = root; this._cache = new Map(); } getColorMap() { return this._colorMap.getColorMap(); } _match(token) { return this._root.match(token); } match(languageId, token) { // The cache contains the metadata without the language bits set. let result = this._cache.get(token); if (typeof result === 'undefined') { const rule = this._match(token); const standardToken = toStandardTokenType(token); result = (rule.metadata | (standardToken << 8 /* MetadataConsts.TOKEN_TYPE_OFFSET */)) >>> 0; this._cache.set(token, result); } return (result | (languageId << 0 /* MetadataConsts.LANGUAGEID_OFFSET */)) >>> 0; } } const STANDARD_TOKEN_TYPE_REGEXP = /\b(comment|string|regex|regexp)\b/; export function toStandardTokenType(tokenType) { const m = tokenType.match(STANDARD_TOKEN_TYPE_REGEXP); if (!m) { return 0 /* StandardTokenType.Other */; } switch (m[1]) { case 'comment': return 1 /* StandardTokenType.Comment */; case 'string': return 2 /* StandardTokenType.String */; case 'regex': return 3 /* StandardTokenType.RegEx */; case 'regexp': return 3 /* StandardTokenType.RegEx */; } throw new Error('Unexpected match for standard token type!'); } export function strcmp(a, b) { if (a < b) { return -1; } if (a > b) { return 1; } return 0; } export class ThemeTrieElementRule { constructor(fontStyle, foreground, background) { this._themeTrieElementRuleBrand = undefined; this._fontStyle = fontStyle; this._foreground = foreground; this._background = background; this.metadata = ((this._fontStyle << 11 /* MetadataConsts.FONT_STYLE_OFFSET */) | (this._foreground << 15 /* MetadataConsts.FOREGROUND_OFFSET */) | (this._background << 24 /* MetadataConsts.BACKGROUND_OFFSET */)) >>> 0; } clone() { return new ThemeTrieElementRule(this._fontStyle, this._foreground, this._background); } acceptOverwrite(fontStyle, foreground, background) { if (fontStyle !== -1 /* FontStyle.NotSet */) { this._fontStyle = fontStyle; } if (foreground !== 0 /* ColorId.None */) { this._foreground = foreground; } if (background !== 0 /* ColorId.None */) { this._background = background; } this.metadata = ((this._fontStyle << 11 /* MetadataConsts.FONT_STYLE_OFFSET */) | (this._foreground << 15 /* MetadataConsts.FOREGROUND_OFFSET */) | (this._background << 24 /* MetadataConsts.BACKGROUND_OFFSET */)) >>> 0; } } export class ThemeTrieElement { constructor(mainRule) { this._themeTrieElementBrand = undefined; this._mainRule = mainRule; this._children = new Map(); } match(token) { if (token === '') { return this._mainRule; } const dotIndex = token.indexOf('.'); let head; let tail; if (dotIndex === -1) { head = token; tail = ''; } else { head = token.substring(0, dotIndex); tail = token.substring(dotIndex + 1); } const child = this._children.get(head); if (typeof child !== 'undefined') { return child.match(tail); } return this._mainRule; } insert(token, fontStyle, foreground, background) { if (token === '') { // Merge into the main rule this._mainRule.acceptOverwrite(fontStyle, foreground, background); return; } const dotIndex = token.indexOf('.'); let head; let tail; if (dotIndex === -1) { head = token; tail = ''; } else { head = token.substring(0, dotIndex); tail = token.substring(dotIndex + 1); } let child = this._children.get(head); if (typeof child === 'undefined') { child = new ThemeTrieElement(this._mainRule.clone()); this._children.set(head, child); } child.insert(tail, fontStyle, foreground, background); } } export function generateTokensCSSForColorMap(colorMap) { const rules = []; for (let i = 1, len = colorMap.length; i < len; i++) { const color = colorMap[i]; rules[i] = `.mtk${i} { color: ${color}; }`; } rules.push('.mtki { font-style: italic; }'); rules.push('.mtkb { font-weight: bold; }'); rules.push('.mtku { text-decoration: underline; text-underline-position: under; }'); rules.push('.mtks { text-decoration: line-through; }'); rules.push('.mtks.mtku { text-decoration: underline line-through; text-underline-position: under; }'); return rules.join('\n'); }