Teach you how to use vscode theme in Monaco editor

Time:2022-4-30

background

The author opened a small projectcode-run, similarcodepenA tool in which the code editor is MicrosoftMonaco Editor, this library is directly fromVSCodeGenerated in the source code of, just made a little modification to make it support running in the browser, but the function is basically the same asVSCodeJust as powerful, so in my opinionMonaco Editorbe equal toVSCodeEditor core.

In addition, the author is a face control. No matter what project he does, he is keen to support some good-looking skin and themes, soMoncao EditorOnly three built-in themes are far from meeting the needs of the author. Moreover, they are ugly, so they are combinedMonaco EditorandVSCodeIt’s natural to think about the relationship between them. Can they be reused directlyVSCodeNext, let’s introduce the writer’s way of exploration.

PS. if you want to know how to implement it directly, you can jump to the [specific implementation] section.

Basic use

Have a look firstMonaco EditorFor basic use, first install:

npm install monaco-editor

Then introduce:

import * as monaco from 'monaco-editor'

//Create a JS editor
const editor = monaco.editor.create(document.getElementById('container'), {
    value: ['function x() {', '\tconsole.log("Hello world!");', '}'].join('\n'),
    language: 'javascript',
    theme: 'vs'
})

So you cancontainerCreate a on the elementjsLanguage editor, and uses the built-invs-darkTheme. If you encounter an error or the syntax prompt does not take effect, you may need to configure itworkerThe path of the file can refer to the official examplebrowser-esm-webpack

Custom theme

Monaco EditorSupport custom themes as follows:

//Define theme
monaco.editor.defineTheme(themeName, themeData)
//Use defined topics
monaco.editor.setTheme(themeName)

themeNameIs the name of the topic to be customized, such asOneDarkProthemeDataIs an object, i.e. subject data. The basic structure is as follows:

{
    Base: 'vs', // the basic topics to be inherited, namely the built-in three: vs, vs dark and HC black
    Inherit: false, // inherit
    Rules: [// highlight rules, that is, set different display styles for codes with different token types in the code
        { token: '', foreground: '000000', background: 'fffffe' }
    ],
    Colors: {// the colors of other parts of non code parts, such as background, scroll bar, etc
        [editorBackground]: '#FFFFFE'
    }
}

rulesIt is used to highlight the code, which is commontokenhavestring(string)comment(note)keyword(key words) wait, complete, please movethemes.tsthesetokenHow are you sure,Monaco EditorBuilt in a syntax shaderMonarch, the essence is to match through regular expression, and then name the matched content as atoken

You can directly view the corresponding code in the editortoken, pressF1Or right clickCommand Palette, then find and clickDeveloper: Inspect TokensNext, click the code and the corresponding information will be displayed, includingtokenType, currently applied color, etc.

Trample pit

The initial idea is very simple. Find it directlyVSCodeAnd then use it by customizing the theme.

obtainVSCodetopic file

There are two ways if a topic is already in yourVSCodeIf it is installed and in use, you can pressF1orCommand/Control + Shift + POr right clickCommand palette / command panel, then find and clickDeveloper: generate color theme from current setting, thenVSCodeA copy will be generatedjsonData, save it.

If a theme is not installed, you can goVscode store themeSearch for the topic, enter the topic details page and click the on the rightDownload ExtensionButton to download the theme. After downloading, find the file you just downloaded. The file should be.vsixChange the suffix to direct.zip, then unzip, and finally open the inside/extension/themes/Folder, inside.jsonThe file is the theme file. Open the file and copy itjsonData is enough.

holdVSCodeTopic conversion toMonaco EditorTheme format

After the last step, you should findVSCodeThe format of the topic is as follows:

{
    "$schema": "vscode://schemas/color-theme",
    "type": "dark",
    "colors": {
        "activityBar.background": "#282c34"
    },
    "tokenColors": [
        {
            "scope": "variable.other.generic-type.haskell",
            "settings": {
                "foreground": "#C678DD"
            }
        },
        {
            "scope": [
                "punctuation.section.embedded.begin.php",
                "punctuation.section.embedded.end.php"
            ],
            "settings": {
                "foreground": "#BE5046"
            }
        }
    ]
}  

FollowMonaco EditorThe theme format of is a little different. Can I write a conversion method to convert it into the following:

{
    base: 'vs',
    inherit: false,
    rules: [
        { token: 'variable.other.generic-type.haskell', foreground: '#C678DD' },
        { token: 'punctuation.section.embedded.begin.php', foreground: '#BE5046' },
        { token: 'punctuation.section.embedded.end.php', foreground: '#BE5046' }
    ],
    colors: {
        "activityBar.background": "#282c34"
    }
}

Of course, it’s not difficult, but in the end, when you use this custom theme, you will find that it has no effect. Why? GoMonarchAfter looking at the parsing configuration of the corresponding language, you will find that it is not at allVSCodeThese are defined in the topictoken, it’s strange that it works. What should I do? Do I extend the parsing configuration myself? I did it at the beginning. It shouldn’t be very difficult to write regular expressions. For this reason, I also putMonarchThe document has been translated completelyMonarch ChineseBut when the authorVSCodeWhen you see the following effects in:

Teach you how to use vscode theme in Monaco editor

Give up decisively, which obviously requires semantic analysis. Otherwise, who knowsabcIs a variable.

ActuallyVSCodeSyntax highlighting inTextMate, and inMonaco EditorUsed inMonarch, the two are not the same thing at all. WhyMonaco EditorNot usedTextMateBut to develop a new thing. The reason isVSCodeUsingvscode-textmateTo analyzeTextMateSyntax, this library depends on oneOnigurumaRegular expression library, which usesCLanguage development, of course, does not support running on the browser.

Second best

sinceVSCodeThe theme of can’t be used directly, so you can only use as much as you can, becauseMonaco EditorBuilt in themetokenThere’s only so much, so take it alltokenChange the color toVSCodeThe theme color is OK. Although there is no semantic highlight, it is always better than the default theme. The implementation is also very simple. FirstcolorsPart of the basic can be used directly, andtokenPart can be done by the method described aboveDeveloper: Inspect TokensstayVSCodeFind the color of the corresponding code block in and copy it toMonaco EditorTopic correspondencetokenJust go up, such as the one after the author’s conversionOneDarkProThe actual results are as follows:

Teach you how to use vscode theme in Monaco editor

stayVSCodeThe effects in the are as follows:

Teach you how to use vscode theme in Monaco editor

You can only look at it roughly, not carefully.

Someone has already done this. You can refer to this warehousemonaco-themes, it helps you convert some common topics, which can be used directly.

New dawn

Just when the author has given upMonaco EditorDirect use inVSCodeAfter the idea of the theme, I found it inadvertentlycodesandboxandleetcodeEditor themes and effects in both sitesVSCodeBasically consistent, and it can be clearly seen inleetcodeFiles requested for switching topics in:

Teach you how to use vscode theme in Monaco editor

Basic andVSCodeThe theme format is the same, which shows that inMonaco EditorUsed inVSCodeIf the theme can be realized, the problem becomes how to realize it.

realization

I have to say that there is really little information in this regard, and there are basically no relevant articles. There are only one or two relevant links in Baidu search results, but it is enough to solve the problem. See the tail of the article for relevant links.

Mainly used ismonaco-editor-textmateThis tool (so besides Baidu and Google,githubIt is also a very important search engine. First install:

npm i monaco-editor-textmate

npmIt should be installed for you at the same timemonaco-textmateonigasmmonaco-editorThese bags,monaco-editorNeedless to say, we installed them ourselves. The other two can be checked by ourselves. If not, we need to install them by ourselves.

Tool introduction

Briefly introduce these packages.

onigasm

This library is used to solve the problem that the above browsers do not supportCWritten inOnigurumaThe solution to the problem is toOnigurumaCompile asWebAssemblyWebAssemblyIt is an intermediate format, which can put nonjsCode compiled into.wasmFormat, and then the browser can load and run it,WebAssemblyAlreadyWEBAs time goes by, I believe compatibility is not a problem.

monaco-textmate

This library is inVSCodeUsedvscode-textmateLibrary, so that it can be used in the browser. The main function is to analyzeTextMateSyntax, this library depends on the previousonigasm

monaco-editor-textmate

The main function of this library is to help usmonaco-editorandmonaco-textmateAssociated, the corresponding language will be loaded internally firstTextMateSyntax file, and then callmonaco.languages.setTokensProviderMethod from the definition languagetokenParser.

Take a look at its usage example:

import { loadWASM } from 'onigasm'
import { Registry } from 'monaco-textmate'
import { wireTmGrammars } from 'monaco-editor-textmate'
export async function liftOff() {
    await loadWASM(`path/to/onigasm.wasm`)
    const registry = new Registry({
        getGrammarDefinition: async (scopeName) => {
            return {
                format: 'json',
                content: await (await fetch(`static/grammars/css.tmGrammar.json`)).text()
            }
        }
    })
    const grammars = new Map()
    grammars.set('css', 'source.css')
    grammars.set('html', 'text.html.basic')
    grammars.set('typescript', 'source.ts')
    monaco.editor.defineTheme('vs-code-theme-converted', {});
    var editor = monaco.editor.create(document.getElementById('container'), {
        value: [
            'html, body {',
            '    margin: 0;',
            '}'
        ].join('\n'),
        language: 'css',
        theme: 'vs-code-theme-converted'
    })
    await wireTmGrammars(monaco, registry, grammars, editor)
}

Concrete implementation

After reading the previous usage examples, let’s take a detailed look at how to use them.

Load onigasm

The first thing we need to do is loadonigasmofwasmFile. This file needs to be loaded first and can be loaded once, so we load it before the initialization of the editor:

import { loadWASM } from 'onigasm'
const init = async () => {
    await loadWASM(`${base}/onigasm/onigasm.wasm`)
    //Create editor
}
init()

onigasm.wasmFiles can be found in/node_modules/onigasm/lib/Find it in the directory, and then copy it to the of the project/public/onigasm/Directory, which can be accessed throughhttpMake a request.

Create scope mapping

Next, create a languageidMapping to scope Name:

const grammars = new Map()
grammars.set('css', 'source.css')

Scope names for other languages can be found inSyntax list of various languagesFind it here, for example, want to knowcssScope name, we entercssDirectory, and then openpackage.jsonFile, you can see one of themgrammarsField:

"grammars": [
    {
        "language": "css",
        "scopeName": "source.css",
        "path": "./syntaxes/css.tmLanguage.json",
        "tokenTypes": {
            "meta.function.url string.quoted": "other"
        }
    }
]

languageIt’s languageidscopeNameIs the scope name. Common are as follows:

const scopeNameMap = {
    html: 'text.html.basic',
    pug: 'text.pug',
    css: 'source.css',
    less: 'source.css.less',
    scss: 'source.css.scss',
    typescript: 'source.ts',
    javascript: 'source.js',
    javascriptreact: 'source.js.jsx',
    coffeescript: 'source.coffee'
}

Register syntax mapping

Then registerTextMateIn this way, the corresponding syntax can be loaded and created by using the domain name:

import {
    Registry
} from 'monaco-textmate'

//Create a registry to load the corresponding syntax file from the domain name
const registry = new Registry({
    getGrammarDefinition: async (scopeName) => {
        return {
            Format: 'JSON', // syntax file format, including JSON and plist
            content: await (await fetch(`${base}grammars/css.tmLanguage.json`)).text()
        }
    }
})

The syntax file, like the previous scope name, is also inSyntax list of various languagesLook here, toocssTake language as an example, or look at itpackage.jsonofgrammarsField:

"grammars": [
    {
        "language": "css",
        "scopeName": "source.css",
        "path": "./syntaxes/css.tmLanguage.json",
        "tokenTypes": {
            "meta.function.url string.quoted": "other"
        }
    }
]

pathField is the path of the corresponding syntax file. We put thesejsonCopy file to project/public/grammars/Directory, so you canfetchCome and ask.

Define theme

As mentioned earlier,Monaco EditorTopic format andVSCodeThe format of is a little different, so it needs to be converted. The conversion can be realized by itself or used directlymonaco-vscode-textmate-theme-converterThis tool can convert multiple local files at the same time:

// convertTheme.js
const converter = require('monaco-vscode-textmate-theme-converter')
const path = require('path')

const run = async () => {
    try {
        await converter.convertThemeFromDir(
            path.resolve(__dirname, './vscodeThemes'), 
            path.resolve(__dirname, '../public/themes')
        );
    } catch (error) {
        console.log(error)
    }
}
run()

functionnode ./convertTheme.jsAfter the order, I’ll put you invscodeThemesAll under the directoryVSCodeConvert theme files intoMonaco EditorAnd output topublic/themesDirectory, and then we pass it directly in the codefetchTo request a theme file and usedefineThemeMethod to define the topic:

//Request onedarkpro theme file
const themeData = await (
    await fetch(`${base}themes/OneDarkPro.json`)
).json()
//Define theme
monaco.editor.defineTheme('OneDarkPro', themeData)

Set token parser

After the previous preparations, the last step is to set upMonaco EditoroftokenThe built-in parser is used by defaultMonarch, we’ll change it toTextMateThe parser, that ismonaco-editor-textmateWhat to do:

import {
    wireTmGrammars
} from 'monaco-editor-textmate'
import * as monaco from 'monaco-editor'

let editor = monaco.editor.create(document.getElementById('container'), {
    value: [
        'html, body {',
        '    margin: 0;',
        '}'
    ].join('\n'),
    language: 'css',
    theme: 'OneDarkPro'
})

await wireTmGrammars(monaco, registry, grammars, editor)

Question 1

You should see it after the previous stepVSCodeThe theme isMonaco EditorIt has taken effect on the, but if you try several times, you may find that it will fail occasionally. The reason isMonaco EditorThe built-in language is delayed loading, and one will also be registered after loadingtokenParser, so our will be overwritten. SeeissuesetTokensProvider unable to override existing tokenizer

One solution is to remove the built-in language, which can be usedmonaco-editor-webpack-plugin

Installation:

npm install monaco-editor-webpack-plugin -D

VueThe project configuration is as follows:

// vue.config.js
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')

module.exports = {
    configureWebpack: {
        plugins: [
            new MonacoWebpackPlugin({
                languages: []
            })
        ]
    }
}

languagesOption is used to specify the language to be included. We directly set it to blank and don’t want anything.

Then modifyMonaco EditorThe import method of is:

import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'

Finally, we need to manually register the language we need, because all built-in languages have been removed. For example, we need to usejsLanguage:

monaco.languages.register({id: 'javascript'})

Although this method can perfectly solve this problem, a big side effect is that the syntax prompt does not take effect, because only the built-inhtmlcsstypescriptWill load the correspondingworkerThe author can’t accept the document without syntax prompt, so the author uses a comparison method in the endlowofhackMethod:

//Plug in configuration
new MonacoWebpackPlugin({
    languages: ['css', 'html', 'javascript', 'less', 'pug', 'scss', 'typescript', 'coffee']
})

//Comment out the language registration statement
// monaco.languages.register({id: 'javascript'})

//When the worker file is loaded, the wire
let hasGetAllWorkUrl = false
window.MonacoEnvironment = {
    getWorkerUrl: function (moduleId, label) {
        hasGetAllWorkUrl = true
        if (label === 'json') {
            return './monaco/json.worker.bundle.js'
        }
        if (label === 'css' || label === 'scss' || label === 'less') {
            return './monaco/css.worker.bundle.js'
        }
        if (label === 'html' || label === 'handlebars' || label === 'razor') {
            return './monaco/html.worker.bundle.js'
        }
        if (label === 'typescript' || label === 'javascript') {
            return './monaco/ts.worker.bundle.js'
        }
        return './monaco/editor.worker.bundle.js'
    },
}
//Cycle detection
let loop = () => {
    if (hasGetAllWorkUrl) {
        Promise.resolve().then(async () => {
            await wireTmGrammars(monaco, registry, grammars, editor)
        })
    } else {
        setTimeout(() => {
            loop()
        }, 100)
    }
}
loop()

Question 2

Another problem encountered by the author is that the default colors of some themes after conversion are not set, so they are black and ugly:

Teach you how to use vscode theme in Monaco editor

The solution to this problem can be given to the subjectrulesAdd an empty to the arraytokenUsed as the default for unmatchedtoken

{
    "rules": [
        {
            "foreground": "#abb2bf",
            "token": ""
        }
     ]
}

foregroundThe color value of can be taken ascolorsIn the optionseditor.foregroundIt is troublesome to manually modify each color value, which can be carried out in the previous steps of converting the theme, and will be solved together in the next problem.

Question 3

monaco-vscode-textmate-theme-converterThis bag is essentiallynodejsEnvironment, so it is not convenient to use it in the pure front-end environment. In addition, it is not suitable for non-standardjsonFormattedVSCodeErrors will be reported during topic conversion, because many topic formats are.jsonc, the content has many comments, so you need to check and modify it yourself first, which is not very convenient. Based on these two problems, the authorforkIts code is then modified and divided into two packages, corresponding to each othernodejsandbrowserEnvironment, seehttps://github.com/wanglin2/monaco-vscode-textmate-theme-converter

So we can replace itmonaco-vscode-textmate-theme-converter, change to the author’s:

npm i vscode-theme-to-monaco-theme-node -D

The usage is basically the same:

//Just modify the package imported as the author
const converter = require('vscode-theme-to-monaco-theme-node')
const path = require('path')

const run = async () => {
    try {
        await converter.convertThemeFromDir(
            path.resolve(__dirname, './vscodeThemes'), 
            path.resolve(__dirname, '../public/themes')
        );
    } catch (error) {
        console.log(error)
    }
}
run()

You can convert directly now.jsoncFile, and the output is unified as.jsonIn addition, an empty file will be automatically added insidetokenAs the default without matchingtoken, the effect is as follows:

Teach you how to use vscode theme in Monaco editor

Best practices

VSCodeIn addition to the code theme, the theme generally includes the themes of other parts of the editor, such as title bar, status bar, sidebar, button, etc., so we can also apply these styles on the page to achieve the effect that the theme of the whole page can also be switched with the code theme of the editor, which can make the page more coordinated as a whole. For specific implementation, we can use these stylesCSSVariable, first define all the colors involved in the page asCSSVariable, and then when switching themes, according to thecolorsThe specified field in the option can be used to update the variable. The specific field used to correspond to which part of the page can be determined according to the actual situation,VSCodeAll configurable items of the topic can be intheme-colorFind it here. The effect is as follows:

Teach you how to use vscode theme in Monaco editor

summary

This paper introduces the author’s views onMonaco EditorFor the exploration of editor theme, I hope to give some help to the partners who need to customize the theme. For the complete code, please refer to the source code of this project:code-run

Reference link

article:Monaco uses vscode related syntax to highlight on the browser

article:How does codesandbox solve the problem of theme

article:Chat about Monaco editor – monarch of custom language

Discussion:How do I use VSC themes in Monaco editor?

Discussion:Use webassembly to support Textmate syntax