UNPKG

monaco-editor

Version:
346 lines (345 loc) • 14 kB
/*--------------------------------------------------------------------------------------------- * 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 { addMatchMediaChangeListener } from '../../../base/browser/browser.js'; import { Color } from '../../../base/common/color.js'; import { Emitter } from '../../../base/common/event.js'; import { TokenizationRegistry } from '../../common/languages.js'; import { TokenMetadata } from '../../common/encodedTokenAttributes.js'; import { TokenTheme, generateTokensCSSForColorMap } from '../../common/languages/supports/tokenization.js'; import { hc_black, hc_light, 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, isDark, isHighContrast } from '../../../platform/theme/common/theme.js'; import { getIconsStyleSheet, UnthemedProductIconTheme } from '../../../platform/theme/browser/iconsStyleSheet.js'; import { mainWindow } from '../../../base/browser/window.js'; export const VS_LIGHT_THEME_NAME = 'vs'; export const VS_DARK_THEME_NAME = 'vs-dark'; export const HC_BLACK_THEME_NAME = 'hc-black'; export const HC_LIGHT_THEME_NAME = 'hc-light'; const colorRegistry = Registry.as(Extensions.ColorContribution); const themingRegistry = Registry.as(ThemingExtensions.ThemingContribution); class StandaloneTheme { constructor(name, standaloneThemeData) { this.semanticHighlighting = false; this.themeData = standaloneThemeData; const 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 (const id in this.themeData.colors) { colors.set(id, Color.fromHex(this.themeData.colors[id])); } if (this.themeData.inherit) { const baseData = getBuiltinRules(this.themeData.base); for (const 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 this.getColors().has(colorId); } get type() { switch (this.base) { case VS_LIGHT_THEME_NAME: return ColorScheme.LIGHT; case HC_BLACK_THEME_NAME: return ColorScheme.HIGH_CONTRAST_DARK; case HC_LIGHT_THEME_NAME: return ColorScheme.HIGH_CONTRAST_LIGHT; default: return ColorScheme.DARK; } } get tokenTheme() { if (!this._tokenTheme) { let rules = []; let encodedTokensColors = []; if (this.themeData.inherit) { const 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 /* FontStyle.Italic */), bold: Boolean(fontStyle & 2 /* FontStyle.Bold */), underline: Boolean(fontStyle & 4 /* FontStyle.Underline */), strikethrough: Boolean(fontStyle & 8 /* FontStyle.Strikethrough */) }; } } function isBuiltinTheme(themeName) { return (themeName === VS_LIGHT_THEME_NAME || themeName === VS_DARK_THEME_NAME || themeName === HC_BLACK_THEME_NAME || themeName === HC_LIGHT_THEME_NAME); } function getBuiltinRules(builtinTheme) { switch (builtinTheme) { case VS_LIGHT_THEME_NAME: return vs; case VS_DARK_THEME_NAME: return vs_dark; case HC_BLACK_THEME_NAME: return hc_black; case HC_LIGHT_THEME_NAME: return hc_light; } } function newBuiltInTheme(builtinTheme) { const themeData = getBuiltinRules(builtinTheme); return new StandaloneTheme(builtinTheme, themeData); } export class StandaloneThemeService extends Disposable { constructor() { super(); this._onColorThemeChange = this._register(new Emitter()); this.onDidColorThemeChange = this._onColorThemeChange.event; this._onProductIconThemeChange = this._register(new Emitter()); this.onDidProductIconThemeChange = this._onProductIconThemeChange.event; this._environment = Object.create(null); this._builtInProductIconTheme = new UnthemedProductIconTheme(); this._autoDetectHighContrast = true; this._knownThemes = new Map(); this._knownThemes.set(VS_LIGHT_THEME_NAME, newBuiltInTheme(VS_LIGHT_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)); this._knownThemes.set(HC_LIGHT_THEME_NAME, newBuiltInTheme(HC_LIGHT_THEME_NAME)); const iconsStyleSheet = this._register(getIconsStyleSheet(this)); this._codiconCSS = iconsStyleSheet.getCSS(); this._themeCSS = ''; this._allCSS = `${this._codiconCSS}\n${this._themeCSS}`; this._globalStyleElement = null; this._styleElements = []; this._colorMapOverride = null; this.setTheme(VS_LIGHT_THEME_NAME); this._onOSSchemeChanged(); this._register(iconsStyleSheet.onDidChange(() => { this._codiconCSS = iconsStyleSheet.getCSS(); this._updateCSS(); })); addMatchMediaChangeListener(mainWindow, '(forced-colors: active)', () => { this._onOSSchemeChanged(); }); } registerEditorContainer(domNode) { if (dom.isInShadowDOM(domNode)) { return this._registerShadowDomContainer(domNode); } return this._registerRegularEditorContainer(); } _registerRegularEditorContainer() { if (!this._globalStyleElement) { this._globalStyleElement = dom.createStyleSheet(undefined, style => { style.className = 'monaco-colors'; style.textContent = this._allCSS; }); this._styleElements.push(this._globalStyleElement); } return Disposable.None; } _registerShadowDomContainer(domNode) { const styleElement = dom.createStyleSheet(domNode, style => { style.className = 'monaco-colors'; style.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_LIGHT_THEME_NAME); } this._updateActualTheme(theme); } _updateActualTheme(desiredTheme) { if (!desiredTheme || this._theme === desiredTheme) { // Nothing to do return; } this._theme = desiredTheme; this._updateThemeOrColorMap(); } _onOSSchemeChanged() { if (this._autoDetectHighContrast) { const wantsHighContrast = mainWindow.matchMedia(`(forced-colors: active)`).matches; if (wantsHighContrast !== isHighContrast(this._theme.type)) { // switch to high contrast or non-high contrast but stick to dark or light let newThemeName; if (isDark(this._theme.type)) { newThemeName = wantsHighContrast ? HC_BLACK_THEME_NAME : VS_DARK_THEME_NAME; } else { newThemeName = wantsHighContrast ? HC_LIGHT_THEME_NAME : VS_LIGHT_THEME_NAME; } this._updateActualTheme(this._knownThemes.get(newThemeName)); } } } setAutoDetectHighContrast(autoDetectHighContrast) { this._autoDetectHighContrast = autoDetectHighContrast; this._onOSSchemeChanged(); } _updateThemeOrColorMap() { const cssRules = []; const hasRule = {}; const 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, .monaco-diff-editor, .monaco-component { ${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 }; } getProductIconTheme() { return this._builtInProductIconTheme; } }