/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../base/browser/dom.js'; import { Color } from '../../../base/common/color.js'; import { Emitter } from '../../../base/common/event.js'; import { TokenizationRegistry, TokenMetadata } from '../../common/modes.js'; import { TokenTheme, generateTokensCSSForColorMap } from '../../common/modes/supports/tokenization.js'; import { hc_black, vs, vs_dark } from '../common/themes.js'; import { Registry } from '../../../platform/registry/common/platform.js'; import { asCssVariableName, Extensions } from '../../../platform/theme/common/colorRegistry.js'; import { Extensions as ThemingExtensions } from '../../../platform/theme/common/themeService.js'; import { Disposable } from '../../../base/common/lifecycle.js'; import { ColorScheme } from '../../../platform/theme/common/theme.js'; import { getIconsStyleSheet } from '../../../platform/theme/browser/iconsStyleSheet.js'; const VS_THEME_NAME = 'vs'; const VS_DARK_THEME_NAME = 'vs-dark'; const HC_BLACK_THEME_NAME = 'hc-black'; const colorRegistry = Registry.as(Extensions.ColorContribution); const themingRegistry = Registry.as(ThemingExtensions.ThemingContribution); class StandaloneTheme { constructor(name, standaloneThemeData) { this.semanticHighlighting = false; this.themeData = standaloneThemeData; let base = standaloneThemeData.base; if (name.length > 0) { if (isBuiltinTheme(name)) { this.id = name; } else { this.id = base + ' ' + name; } this.themeName = name; } else { this.id = base; this.themeName = base; } this.colors = null; this.defaultColors = Object.create(null); this._tokenTheme = null; } get base() { return this.themeData.base; } notifyBaseUpdated() { if (this.themeData.inherit) { this.colors = null; this._tokenTheme = null; } } getColors() { if (!this.colors) { const colors = new Map(); for (let id in this.themeData.colors) { colors.set(id, Color.fromHex(this.themeData.colors[id])); } if (this.themeData.inherit) { let baseData = getBuiltinRules(this.themeData.base); for (let id in baseData.colors) { if (!colors.has(id)) { colors.set(id, Color.fromHex(baseData.colors[id])); } } } this.colors = colors; } return this.colors; } getColor(colorId, useDefault) { const color = this.getColors().get(colorId); if (color) { return color; } if (useDefault !== false) { return this.getDefault(colorId); } return undefined; } getDefault(colorId) { let color = this.defaultColors[colorId]; if (color) { return color; } color = colorRegistry.resolveDefaultColor(colorId, this); this.defaultColors[colorId] = color; return color; } defines(colorId) { return Object.prototype.hasOwnProperty.call(this.getColors(), colorId); } get type() { switch (this.base) { case VS_THEME_NAME: return ColorScheme.LIGHT; case HC_BLACK_THEME_NAME: return ColorScheme.HIGH_CONTRAST; default: return ColorScheme.DARK; } } get tokenTheme() { if (!this._tokenTheme) { let rules = []; let encodedTokensColors = []; if (this.themeData.inherit) { let baseData = getBuiltinRules(this.themeData.base); rules = baseData.rules; if (baseData.encodedTokensColors) { encodedTokensColors = baseData.encodedTokensColors; } } // Pick up default colors from `editor.foreground` and `editor.background` if available const editorForeground = this.themeData.colors['editor.foreground']; const editorBackground = this.themeData.colors['editor.background']; if (editorForeground || editorBackground) { const rule = { token: '' }; if (editorForeground) { rule.foreground = editorForeground; } if (editorBackground) { rule.background = editorBackground; } rules.push(rule); } rules = rules.concat(this.themeData.rules); if (this.themeData.encodedTokensColors) { encodedTokensColors = this.themeData.encodedTokensColors; } this._tokenTheme = TokenTheme.createFromRawTokenTheme(rules, encodedTokensColors); } return this._tokenTheme; } getTokenStyleMetadata(type, modifiers, modelLanguage) { // use theme rules match const style = this.tokenTheme._match([type].concat(modifiers).join('.')); const metadata = style.metadata; const foreground = TokenMetadata.getForeground(metadata); const fontStyle = TokenMetadata.getFontStyle(metadata); return { foreground: foreground, italic: Boolean(fontStyle & 1 /* Italic */), bold: Boolean(fontStyle & 2 /* Bold */), underline: Boolean(fontStyle & 4 /* Underline */) }; } } function isBuiltinTheme(themeName) { return (themeName === VS_THEME_NAME || themeName === VS_DARK_THEME_NAME || themeName === HC_BLACK_THEME_NAME); } function getBuiltinRules(builtinTheme) { switch (builtinTheme) { case VS_THEME_NAME: return vs; case VS_DARK_THEME_NAME: return vs_dark; case HC_BLACK_THEME_NAME: return hc_black; } } function newBuiltInTheme(builtinTheme) { let themeData = getBuiltinRules(builtinTheme); return new StandaloneTheme(builtinTheme, themeData); } export class StandaloneThemeServiceImpl extends Disposable { constructor() { super(); this._onColorThemeChange = this._register(new Emitter()); this.onDidColorThemeChange = this._onColorThemeChange.event; this._environment = Object.create(null); this._autoDetectHighContrast = true; this._knownThemes = new Map(); this._knownThemes.set(VS_THEME_NAME, newBuiltInTheme(VS_THEME_NAME)); this._knownThemes.set(VS_DARK_THEME_NAME, newBuiltInTheme(VS_DARK_THEME_NAME)); this._knownThemes.set(HC_BLACK_THEME_NAME, newBuiltInTheme(HC_BLACK_THEME_NAME)); const iconsStyleSheet = getIconsStyleSheet(); this._codiconCSS = iconsStyleSheet.getCSS(); this._themeCSS = ''; this._allCSS = `${this._codiconCSS}\n${this._themeCSS}`; this._globalStyleElement = null; this._styleElements = []; this._colorMapOverride = null; this.setTheme(VS_THEME_NAME); iconsStyleSheet.onDidChange(() => { this._codiconCSS = iconsStyleSheet.getCSS(); this._updateCSS(); }); dom.addMatchMediaChangeListener('(forced-colors: active)', () => { this._updateActualTheme(); }); } registerEditorContainer(domNode) { if (dom.isInShadowDOM(domNode)) { return this._registerShadowDomContainer(domNode); } return this._registerRegularEditorContainer(); } _registerRegularEditorContainer() { if (!this._globalStyleElement) { this._globalStyleElement = dom.createStyleSheet(); this._globalStyleElement.className = 'monaco-colors'; this._globalStyleElement.textContent = this._allCSS; this._styleElements.push(this._globalStyleElement); } return Disposable.None; } _registerShadowDomContainer(domNode) { const styleElement = dom.createStyleSheet(domNode); styleElement.className = 'monaco-colors'; styleElement.textContent = this._allCSS; this._styleElements.push(styleElement); return { dispose: () => { for (let i = 0; i < this._styleElements.length; i++) { if (this._styleElements[i] === styleElement) { this._styleElements.splice(i, 1); return; } } } }; } defineTheme(themeName, themeData) { if (!/^[a-z0-9\-]+$/i.test(themeName)) { throw new Error('Illegal theme name!'); } if (!isBuiltinTheme(themeData.base) && !isBuiltinTheme(themeName)) { throw new Error('Illegal theme base!'); } // set or replace theme this._knownThemes.set(themeName, new StandaloneTheme(themeName, themeData)); if (isBuiltinTheme(themeName)) { this._knownThemes.forEach(theme => { if (theme.base === themeName) { theme.notifyBaseUpdated(); } }); } if (this._theme.themeName === themeName) { this.setTheme(themeName); // refresh theme } } getColorTheme() { return this._theme; } setColorMapOverride(colorMapOverride) { this._colorMapOverride = colorMapOverride; this._updateThemeOrColorMap(); } setTheme(themeName) { let theme; if (this._knownThemes.has(themeName)) { theme = this._knownThemes.get(themeName); } else { theme = this._knownThemes.get(VS_THEME_NAME); } this._desiredTheme = theme; this._updateActualTheme(); } _updateActualTheme() { const theme = (this._autoDetectHighContrast && window.matchMedia(`(forced-colors: active)`).matches ? this._knownThemes.get(HC_BLACK_THEME_NAME) : this._desiredTheme); if (this._theme === theme) { // Nothing to do return; } this._theme = theme; this._updateThemeOrColorMap(); } setAutoDetectHighContrast(autoDetectHighContrast) { this._autoDetectHighContrast = autoDetectHighContrast; this._updateActualTheme(); } _updateThemeOrColorMap() { let cssRules = []; let hasRule = {}; let ruleCollector = { addRule: (rule) => { if (!hasRule[rule]) { cssRules.push(rule); hasRule[rule] = true; } } }; themingRegistry.getThemingParticipants().forEach(p => p(this._theme, ruleCollector, this._environment)); const colorVariables = []; for (const item of colorRegistry.getColors()) { const color = this._theme.getColor(item.id, true); if (color) { colorVariables.push(`${asCssVariableName(item.id)}: ${color.toString()};`); } } ruleCollector.addRule(`.monaco-editor { ${colorVariables.join('\n')} }`); const colorMap = this._colorMapOverride || this._theme.tokenTheme.getColorMap(); ruleCollector.addRule(generateTokensCSSForColorMap(colorMap)); this._themeCSS = cssRules.join('\n'); this._updateCSS(); TokenizationRegistry.setColorMap(colorMap); this._onColorThemeChange.fire(this._theme); } _updateCSS() { this._allCSS = `${this._codiconCSS}\n${this._themeCSS}`; this._styleElements.forEach(styleElement => styleElement.textContent = this._allCSS); } getFileIconTheme() { return { hasFileIcons: false, hasFolderIcons: false, hidesExplorerArrows: false }; } }