4.0 KB91 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8/**
9 * This is the prefix used by Monaco for the CSS classes it uses for syntax
10 * highlighting. Each token returned by `IGrammar.tokenizeLine2()` has a
11 * `number` associated with it. To construct the appropriate CSS class name for
12 * color number `n`, do: `${CSS_CLASS_PREFIX}${n}`.
13 */
14export const CSS_CLASS_PREFIX = 'mtk';
15
16/**
17 * Updates the <style> element on the page to define the CSS rules necessary to
18 * honor the user's selected theme.
19 * @param colorMap as returned by `Registry.getColorMap()` where each value in
20 * the array is a CSS hex value, such as "#AA0000".
21 */
22export function updateTextMateGrammarCSS(colorMap: string[]): void {
23 // Note that if the Monaco editor is used on the page, then we also need to do
24 // something like:
25 //
26 // const colorMap = cssColors.map(Color.Format.CSS.parseHex);
27 // TokenizationRegistry.setColorMap(colorMap);
28 //
29 // though that will require loading code from the monaco-editor npm module.
30
31 const css = generateTokensCSSForColorMap(colorMap);
32 const style = getOrCreateStyleElementForColorsCSS();
33 style.innerHTML = css;
34}
35
36let styleElementForTextMateCSS: HTMLStyleElement | null = null;
37
38function getOrCreateStyleElementForColorsCSS(): HTMLStyleElement {
39 // If there is an existing <style> element, then overwrite its contents
40 // rather than create a new one. (Yes, this means that we support only one
41 // theme globally on the page at a time, at least for now.)
42 if (styleElementForTextMateCSS != null) {
43 return styleElementForTextMateCSS;
44 }
45
46 // We want to ensure that our <style> element appears after Monaco's so that
47 // we can override some styles it inserted for the default theme.
48 styleElementForTextMateCSS = document.createElement('style');
49
50 // If an instance of the Monaco editor is being used on the page, then it will
51 // have injected a stylesheet that we need to override. We expect these styles
52 // to be in an element with the class name 'monaco-colors' based on:
53 // https://github.com/microsoft/vscode/blob/f78d84606cd16d75549c82c68888de91d8bdec9f/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts#L206-L214
54 //
55 // However, .monaco-colors may not have been inserted yet (this could be the
56 // case depending on where Monaco is in its own initialization process), so we
57 // just append the <style> tag to <body> so that when .monaco-colors is
58 // inserted to the <head> tag, our <style> tag will be guaranteed to appear
59 // later in the DOM and therefore its styles will take precedence.
60 document.body.appendChild(styleElementForTextMateCSS);
61
62 return styleElementForTextMateCSS;
63}
64
65/**
66 * Adapted from the `generateTokensCSSForColorMap()` implementation in
67 * `monaco-editor/esm/vs/editor/common/modes/supports/tokenization.js`.
68 * Note that the original takes an Array<Color>, but while the Color class has
69 * all sorts of fancy methods like `getRelativeLuminance()` and `blend()`, the
70 * only thing this function needs is its `toString()` method, which formats it
71 * as a CSS hex value, which is what `registry.getColorMap()` returned in the
72 * first place!
73 */
74function generateTokensCSSForColorMap(cssColors: readonly string[]): string {
75 const rules: string[] = [];
76 for (let i = 1, len = cssColors.length; i < len; i++) {
77 const color = cssColors[i];
78 rules[i] = `.${CSS_CLASS_PREFIX}${i} { color: ${color}; }`;
79 }
80 rules.push(`.${CSS_CLASS_PREFIX}i { font-style: italic; }`);
81 rules.push(`.${CSS_CLASS_PREFIX}b { font-weight: bold; }`);
82 rules.push(
83 `.${CSS_CLASS_PREFIX}u { text-decoration: underline; text-underline-position: under; }`,
84 );
85 rules.push(`.${CSS_CLASS_PREFIX}s { text-decoration: line-through; }`);
86 rules.push(
87 `.${CSS_CLASS_PREFIX}s.${CSS_CLASS_PREFIX}u { text-decoration: underline line-through; text-underline-position: under; }`,
88 );
89 return rules.join('\n');
90}
91