background
The author opened a small projectcode-run, similarcodepen
A tool in which the code editor is MicrosoftMonaco Editor, this library is directly fromVSCode
Generated 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 asVSCode
Just as powerful, so in my opinionMonaco Editor
be equal toVSCode
Editor 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 Editor
Only three built-in themes are far from meeting the needs of the author. Moreover, they are ugly, so they are combinedMonaco Editor
andVSCode
It’s natural to think about the relationship between them. Can they be reused directlyVSCode
Next, 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 Editor
For 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 cancontainer
Create a on the elementjs
Language editor, and uses the built-invs-dark
Theme. If you encounter an error or the syntax prompt does not take effect, you may need to configure itworker
The path of the file can refer to the official examplebrowser-esm-webpack。
Custom theme
Monaco Editor
Support custom themes as follows:
//Define theme
monaco.editor.defineTheme(themeName, themeData)
//Use defined topics
monaco.editor.setTheme(themeName)
themeName
Is the name of the topic to be customized, such asOneDarkPro
,themeData
Is 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'
}
}
rules
It is used to highlight the code, which is commontoken
havestring
(string)comment
(note)keyword
(key words) wait, complete, please movethemes.tsthesetoken
How are you sure,Monaco Editor
Built 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
, pressF1
Or right clickCommand Palette
, then find and clickDeveloper: Inspect Tokens
Next, click the code and the corresponding information will be displayed, includingtoken
Type, currently applied color, etc.
Trample pit
The initial idea is very simple. Find it directlyVSCode
And then use it by customizing the theme.
obtainVSCode
topic file
There are two ways if a topic is already in yourVSCode
If it is installed and in use, you can pressF1
orCommand/Control + Shift + P
Or right clickCommand palette / command panel
, then find and clickDeveloper: generate color theme from current setting
, thenVSCode
A copy will be generatedjson
Data, 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 Extension
Button to download the theme. After downloading, find the file you just downloaded. The file should be.vsix
Change the suffix to direct.zip
, then unzip, and finally open the inside/extension/themes/
Folder, inside.json
The file is the theme file. Open the file and copy itjson
Data is enough.
holdVSCode
Topic conversion toMonaco Editor
Theme format
After the last step, you should findVSCode
The 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 Editor
The 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 allVSCode
These 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 putMonarch
The document has been translated completelyMonarch ChineseBut when the authorVSCode
When you see the following effects in:
Give up decisively, which obviously requires semantic analysis. Otherwise, who knowsabc
Is a variable.
ActuallyVSCode
Syntax highlighting inTextMate
, and inMonaco Editor
Used inMonarch
, the two are not the same thing at all. WhyMonaco Editor
Not usedTextMate
But to develop a new thing. The reason isVSCode
Usingvscode-textmateTo analyzeTextMate
Syntax, this library depends on oneOniguruma
Regular expression library, which usesC
Language development, of course, does not support running on the browser.
Second best
sinceVSCode
The theme of can’t be used directly, so you can only use as much as you can, becauseMonaco Editor
Built in themetoken
There’s only so much, so take it alltoken
Change the color toVSCode
The theme color is OK. Although there is no semantic highlight, it is always better than the default theme. The implementation is also very simple. Firstcolors
Part of the basic can be used directly, andtoken
Part can be done by the method described aboveDeveloper: Inspect Tokens
stayVSCode
Find the color of the corresponding code block in and copy it toMonaco Editor
Topic correspondencetoken
Just go up, such as the one after the author’s conversionOneDarkPro
The actual results are as follows:
stayVSCode
The effects in the are as follows:
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 Editor
Direct use inVSCode
After the idea of the theme, I found it inadvertentlycodesandboxandleetcodeEditor themes and effects in both sitesVSCode
Basically consistent, and it can be clearly seen inleetcode
Files requested for switching topics in:
Basic andVSCode
The theme format is the same, which shows that inMonaco Editor
Used inVSCode
If 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,github
It is also a very important search engine. First install:
npm i monaco-editor-textmate
npm
It should be installed for you at the same timemonaco-textmate、onigasm、monaco-editor
These bags,monaco-editor
Needless 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 supportC
Written inOniguruma
The solution to the problem is toOniguruma
Compile asWebAssembly,WebAssembly
It is an intermediate format, which can put nonjs
Code compiled into.wasm
Format, and then the browser can load and run it,WebAssembly
AlreadyWEB
As time goes by, I believe compatibility is not a problem.
monaco-textmate
This library is inVSCode
Usedvscode-textmate
Library, so that it can be used in the browser. The main function is to analyzeTextMate
Syntax, this library depends on the previousonigasm
。
monaco-editor-textmate
The main function of this library is to help usmonaco-editor
andmonaco-textmate
Associated, the corresponding language will be loaded internally firstTextMate
Syntax file, and then callmonaco.languages.setTokensProviderMethod from the definition languagetoken
Parser.
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 loadonigasm
ofwasm
File. 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.wasm
Files 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 throughhttp
Make a request.
Create scope mapping
Next, create a languageid
Mapping 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 knowcss
Scope name, we entercss
Directory, and then openpackage.json
File, you can see one of themgrammars
Field:
"grammars": [
{
"language": "css",
"scopeName": "source.css",
"path": "./syntaxes/css.tmLanguage.json",
"tokenTypes": {
"meta.function.url string.quoted": "other"
}
}
]
language
It’s languageid
,scopeName
Is 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 registerTextMate
In 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, toocss
Take language as an example, or look at itpackage.json
ofgrammars
Field:
"grammars": [
{
"language": "css",
"scopeName": "source.css",
"path": "./syntaxes/css.tmLanguage.json",
"tokenTypes": {
"meta.function.url string.quoted": "other"
}
}
]
path
Field is the path of the corresponding syntax file. We put thesejson
Copy file to project/public/grammars/
Directory, so you canfetch
Come and ask.
Define theme
As mentioned earlier,Monaco Editor
Topic format andVSCode
The 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.js
After the order, I’ll put you invscodeThemes
All under the directoryVSCode
Convert theme files intoMonaco Editor
And output topublic/themes
Directory, and then we pass it directly in the codefetch
To request a theme file and usedefineTheme
Method 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 Editor
oftoken
The built-in parser is used by defaultMonarch
, we’ll change it toTextMate
The parser, that ismonaco-editor-textmate
What 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 stepVSCode
The theme isMonaco Editor
It has taken effect on the, but if you try several times, you may find that it will fail occasionally. The reason isMonaco Editor
The built-in language is delayed loading, and one will also be registered after loadingtoken
Parser, so our will be overwritten. Seeissue
:setTokensProvider 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
Vue
The project configuration is as follows:
// vue.config.js
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
module.exports = {
configureWebpack: {
plugins: [
new MonacoWebpackPlugin({
languages: []
})
]
}
}
languages
Option is used to specify the language to be included. We directly set it to blank and don’t want anything.
Then modifyMonaco Editor
The 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 usejs
Language:
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-inhtml
、css
、typescript
Will load the correspondingworker
The author can’t accept the document without syntax prompt, so the author uses a comparison method in the endlow
ofhack
Method:
//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:
The solution to this problem can be given to the subjectrules
Add an empty to the arraytoken
Used as the default for unmatchedtoken
:
{
"rules": [
{
"foreground": "#abb2bf",
"token": ""
}
]
}
foreground
The color value of can be taken ascolors
In the optionseditor.foreground
It 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 essentiallynodejs
Environment, so it is not convenient to use it in the pure front-end environment. In addition, it is not suitable for non-standardjson
FormattedVSCode
Errors 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 authorfork
Its code is then modified and divided into two packages, corresponding to each othernodejs
andbrowser
Environment, 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.jsonc
File, and the output is unified as.json
In addition, an empty file will be automatically added insidetoken
As the default without matchingtoken
, the effect is as follows:
Best practices
VSCode
In 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 stylesCSS
Variable, first define all the colors involved in the page asCSS
Variable, and then when switching themes, according to thecolors
The 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,VSCode
All configurable items of the topic can be intheme-colorFind it here. The effect is as follows:
summary
This paper introduces the author’s views onMonaco Editor
For 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