How to use a VSC theme in monaco-editor?
Asked Answered
A

3

5

To my understanding monaco-editor and VSCode are using two different formats to define themes.

It seems that an earlier version of VSC primarily used the tmTheme definition format which allowed themes to be converted using this tool (also see a GitHub issue from 2017). However, since VSC is now using a new format to define its themes, I'm wondering whether there is a (simple) way to use existing VSC themes in the Monaco editor.

Thanks a lot for your help! :)

P.S. This GitHub issue comment from 2019 seems to indicate that there is indeed no easy way to do this but hopefully things have changed since then. 🀞

Alphonse answered 29/1, 2021 at 17:33 Comment(0)
L
10

I created a small, POC repo to demonstrate how this can work. It's based on the monaco-vscode-textmate-theme-converter plugin. You can find the repo here and I published a tool to actually convert a theme here. Note, due to some loading issues you might need to do a hard refresh of the demo app before starting.

Lowland answered 16/2, 2021 at 7:29 Comment(1)
you and your tool are live saver, thank you! – Annal
G
2

I use monaco and I found this open source repo which conatins almost all the regualrly used VSC themes like katzenmitch and cloud , which you could use as a npm package. When I went through the code I found that he used JSON format like for cobalt theme it was this code:

{
  "base": "vs-dark",
  "inherit": true,
  "rules": [
    {
      "background": "002240",
      "token": ""
    },
    {
      "foreground": "e1efff",
      "token": "punctuation - (punctuation.definition.string || punctuation.definition.comment)"
    },
    {
      "foreground": "ff628c",
      "token": "constant"
    },
    {
      "foreground": "ffdd00",
      "token": "entity"
    },
    {
      "foreground": "ff9d00",
      "token": "keyword"
    },
    {
      "foreground": "ffee80",
      "token": "storage"
    },
    {
      "foreground": "3ad900",
      "token": "string -string.unquoted.old-plist -string.unquoted.heredoc"
    },
    {
      "foreground": "3ad900",
      "token": "string.unquoted.heredoc string"
    },
    {
      "foreground": "0088ff",
      "fontStyle": "italic",
      "token": "comment"
    },
    {
      "foreground": "80ffbb",
      "token": "support"
    },
    {
      "foreground": "cccccc",
      "token": "variable"
    },
    {
      "foreground": "ff80e1",
      "token": "variable.language"
    },
    {
      "foreground": "ffee80",
      "token": "meta.function-call"
    },
    {
      "foreground": "f8f8f8",
      "background": "800f00",
      "token": "invalid"
    },
    {
      "foreground": "ffffff",
      "background": "223545",
      "token": "text source"
    },
    {
      "foreground": "ffffff",
      "background": "223545",
      "token": "string.unquoted.heredoc"
    },
    {
      "foreground": "ffffff",
      "background": "223545",
      "token": "source source"
    },
    {
      "foreground": "80fcff",
      "fontStyle": "italic",
      "token": "entity.other.inherited-class"
    },
    {
      "foreground": "9eff80",
      "token": "string.quoted source"
    },
    {
      "foreground": "80ff82",
      "token": "string constant"
    },
    {
      "foreground": "80ffc2",
      "token": "string.regexp"
    },
    {
      "foreground": "edef7d",
      "token": "string variable"
    },
    {
      "foreground": "ffb054",
      "token": "support.function"
    },
    {
      "foreground": "eb939a",
      "token": "support.constant"
    },
    {
      "foreground": "ff1e00",
      "token": "support.type.exception"
    },
    {
      "foreground": "8996a8",
      "token": "meta.preprocessor.c"
    },
    {
      "foreground": "afc4db",
      "token": "meta.preprocessor.c keyword"
    },
    {
      "foreground": "73817d",
      "token": "meta.sgml.html meta.doctype"
    },
    {
      "foreground": "73817d",
      "token": "meta.sgml.html meta.doctype entity"
    },
    {
      "foreground": "73817d",
      "token": "meta.sgml.html meta.doctype string"
    },
    {
      "foreground": "73817d",
      "token": "meta.xml-processing"
    },
    {
      "foreground": "73817d",
      "token": "meta.xml-processing entity"
    },
    {
      "foreground": "73817d",
      "token": "meta.xml-processing string"
    },
    {
      "foreground": "9effff",
      "token": "meta.tag"
    },
    {
      "foreground": "9effff",
      "token": "meta.tag entity"
    },
    {
      "foreground": "9effff",
      "token": "meta.selector.css entity.name.tag"
    },
    {
      "foreground": "ffb454",
      "token": "meta.selector.css entity.other.attribute-name.id"
    },
    {
      "foreground": "5fe461",
      "token": "meta.selector.css entity.other.attribute-name.class"
    },
    {
      "foreground": "9df39f",
      "token": "support.type.property-name.css"
    },
    {
      "foreground": "f6f080",
      "token": "meta.property-group support.constant.property-value.css"
    },
    {
      "foreground": "f6f080",
      "token": "meta.property-value support.constant.property-value.css"
    },
    {
      "foreground": "f6aa11",
      "token": "meta.preprocessor.at-rule keyword.control.at-rule"
    },
    {
      "foreground": "edf080",
      "token": "meta.property-value support.constant.named-color.css"
    },
    {
      "foreground": "edf080",
      "token": "meta.property-value constant"
    },
    {
      "foreground": "eb939a",
      "token": "meta.constructor.argument.css"
    },
    {
      "foreground": "f8f8f8",
      "background": "000e1a",
      "token": "meta.diff"
    },
    {
      "foreground": "f8f8f8",
      "background": "000e1a",
      "token": "meta.diff.header"
    },
    {
      "foreground": "f8f8f8",
      "background": "4c0900",
      "token": "markup.deleted"
    },
    {
      "foreground": "f8f8f8",
      "background": "806f00",
      "token": "markup.changed"
    },
    {
      "foreground": "f8f8f8",
      "background": "154f00",
      "token": "markup.inserted"
    },
    {
      "background": "8fddf630",
      "token": "markup.raw"
    },
    {
      "background": "004480",
      "token": "markup.quote"
    },
    {
      "background": "130d26",
      "token": "markup.list"
    },
    {
      "foreground": "c1afff",
      "fontStyle": "bold",
      "token": "markup.bold"
    },
    {
      "foreground": "b8ffd9",
      "fontStyle": "italic",
      "token": "markup.italic"
    },
    {
      "foreground": "c8e4fd",
      "background": "001221",
      "fontStyle": "bold",
      "token": "markup.heading"
    }
  ],
  "colors": {
    "editor.foreground": "#FFFFFF",
    "editor.background": "#002240",
    "editor.selectionBackground": "#B36539BF",
    "editor.lineHighlightBackground": "#00000059",
    "editorCursor.foreground": "#FFFFFF",
    "editorWhitespace.foreground": "#FFFFFF26"
  }
}

I suggest you could go through it . and make your own theme instead of using default VSC themes but if you still want to use vsc themes there are many repos like this one. Or you could use tools like https://bitwiser.in/monaco-themes/ to generate theme for monaco from tmTheme files that picks colors from theme files and generates a json to be used directly with monaco.editor.defineTheme.

I hope this helped.

Gillispie answered 16/8, 2021 at 5:49 Comment(2)
Do they have the light+ and dark+ themes? This would be very useful... – Valvulitis
Bear in mind that the themes in the linked repository (and tool) do not work for HTML syntax highlighting (see github.com/brijeshb42/monaco-themes/issues/17 ) – Verdha
T
1

The tmTheme format is still supported in theme extensions and converted on import (from what I remember). The primary definition of a theme in an extension, however, is using the approach like shown in dark_plus.json.

It's pretty easy to convert the json format to the structure that's expected by monaco-editor:

export interface Colors { [key: string]: string }
export interface ITokenEntry {
    name?: string;
    scope: string[] | string;
    settings: {
        foreground?: string;
        background?: string;
        fontStyle?: string;
    };
}

// This is the structure of a vscode theme file.
export interface IThemeObject {
    name: string;
    type?: string;
    include?: string;
    colors?: Colors;

    settings?: ITokenEntry[];    // Old style specification.
    tokenColors?: ITokenEntry[]; // This is how it should be done now.
}

These interfaces describe the format of the json file. Older theme definitions use the settings member to define theme colors. In such a case simply set the tokenColors member to the settings member and proceed.

Once the theme has been loaded you can use this static method to load it into monaco-editor:

    /**
     * Updates the theme used by all code editor instances.
     *
     * @param theme The theme name.
     * @param type The base type of the theme.
     * @param values The actual theme values.
     */
    public static updateTheme(theme: string, type: "light" | "dark", values: IThemeObject): void {
        // Convert all color values to CSS hex form.
        const entries: { [key: string]: string } = {};
        for (const [key, value] of Object.entries(values.colors || {})) {
            entries[key] = colorToHex(value) || "";
        }

        const tokenRules: Monaco.ITokenThemeRule[] = [];
        (values.tokenColors || []).forEach((value: ITokenEntry): void => {
            const scopeValue = value.scope || [];
            const scopes = Array.isArray(scopeValue) ? scopeValue : scopeValue.split(",");
            scopes.forEach((scope: string): void => {
                tokenRules.push({
                    token: scope,
                    foreground: colorToHex(value.settings.foreground),
                    background: colorToHex(value.settings.background),
                    fontStyle: value.settings.fontStyle,
                });
            });
        });

        CodeEditor.currentThemeId = theme.replace(/[^a-zA-Z]+/g, "-");
        Monaco.defineTheme(CodeEditor.currentThemeId, {
            base: type === "light" ? "vs" : "vs-dark",
            inherit: true,
            rules: tokenRules,
            colors: entries,
        });

        Monaco.setTheme(CodeEditor.currentThemeId);
    }

CodeEditor is my TS class that wraps the monaco-editor. The function colorToHex is defined as:

import Color from "color";

/**
 * Converts a color string or a color to a hex string.
 *
 * @param color The value to convert.
 *
 * @returns A hex string of the given color, including the alpha value.
 */
export const colorToHex = (color: string | Color | undefined): string | undefined => {
    if (!color) {
        return;
    }

    if (typeof color === "string") {
        color = new Color(color);
    }

    // Hex color values have no alpha component, so we have to add that explicitly.
    if (color.alpha() < 1) {
        let alpha = Math.round((color.alpha() * 255)).toString(16);
        if (alpha.length < 2) {
            alpha = "0" + alpha;
        }

        return color.hex() + alpha;
    } else {
        return color.hex();
    }
};
Textile answered 30/1, 2021 at 10:46 Comment(7)
Thanks a lot for your response Mike. While the converted theme (VSC -> Monaco) works, there are still many differences in the final rendering result. Here's an example: - VSC screenshot (desired result): i.imgur.com/OXlCmJN.png - Monaco screenshot: i.imgur.com/A9gTAy2.png The working code can be found here: repl.it/@schickling/monaco-vite#src/main.ts – Alphonse
You cannot have all the same colors, because vscode adds semantic highlighting on top of syntax highlighting. You can see that, for example, by the different colors for two identifiers (the function name and the parameters). Monaco doesn't support semantic highlighting out of the box. – Textile
Thanks for the clarification. This incompatibility seems quite unfortunate given VSCode and Monaco are based on the same code base. Hopefully this limitation will be addressed by the maintainers soon. – Alphonse
Hey! I'm trying to figure out the exact same thing right now so if you find an answer I would greatly appreciate you sharing. And just so I don't come to the table empty handed, I am assuming you have read how codesandbox get around this from Ives' post here: codesandbox.io/post/introducing-themes#how-it-works – Lowland
I also found this package and was quite hopeful but could not seem to get it working: github.com/Nishkalkashyap/… – Lowland
Mike would you mind explaining how semantic highlighting works a bit more? I don't understand the issue there fully – Lowland
Sorry for all the comments but I also asked a similar question a day or so before you and there I was thinking of using css as a solution but as Mike pointed out that is not a good approach. #65921679 – Lowland

© 2022 - 2024 β€” McMap. All rights reserved.