文章目录
在很多需要为用户提供编写代码能力的服务中,我们需要给用户提供一下能力足够强大的编辑器,这样可以使得用户在编写代码时更顺畅,减少查询文档次数,降低出错概率,提升编码效率。
而想要提供这些能力,就需要一个足够强大的代码编辑器,并且为它添加我们自定义语言的支持,这样用户可以在语法高亮、自动完成等方面得到足够的支持。
为什么用 Monaco Editor?
Visual Studio Code 是世界一个非常流行的代码编辑器,而 Monaco Editor 是用于构建 VSCode 核心功能的代码编辑器,它提供了相当多的功能,用于实现各种代码编辑能力。并且微软为 Monaco Editor 提供了单独的项目,单独的打包脚本,因此我们可以轻易的将 Monaco Editor 集成到我们自己的 Web 应用中。
Monaco Editor 已经提供了一系列的基础设施,用于完成对自定义语言的支持,只需要通过很小的步骤,我们就可以搭建一个属于自己语言的代码编辑器。
那么,这就开始吧!
Step 1. 注册一个语言
这里将不再赘述如何将 Monaco Editor 引入 Web 应用,在 Monaco Editor 的仓库提供了使用各种方式集成 Monaco Editor 的说明。
为了让 Monaco Editor 知道我们将要添加一种自定义语言的支持,首先需要注册一个自定义语言标识,这里我们选择 mylang
作为我们自定义语言的标识符。
注册一个自定义语言对于 Monaco Editor 来说非常简单,只需一行代码即可完成:
monaco.languages.register({ id: 'mylang' });
全部完成!
当然,这只是添加自定义语言支持的第一步,这里只是让 Monaco Editor 知道了我们需要添加一个名为 mylang
的自定义语言。后面还有更多工作来让这个自定义语言在 Monaco Editor 中更像一个得到完全支持的语言。
Step 2. 添加关键字
接下来,我们需要给 mylang
这个自定义语言来添加关键字的高亮支持。
通常,我们要在一个代码编辑器中添加自定义语言的支持,最重要的就是获得在编写代码时,可以将各种关键字、字符串、注释等来内容具体语法高亮显示,从而可以更清晰地来阅读代码。
在这一步中,需要涉及到的内容会稍多一些,因为关键字、字符串、注释的高亮会涉及到关键字的定义、字符串的定义,以及注释的定义等,并且这些内容在被识别出来之后,使用什么颜色来显示它,又会涉及到 Monaco Editor 中的主题,它将会定义每一个自定义语言元素的颜色、风格等。
我们先来列出 mylang
包含的关键字列表,通过这个列表就是一系列单词的数组:
let keywords = [ 'class', 'new', 'string', 'number', 'boolean', 'private', 'public' ];
在匹配关键字时,需要注意通常关键字的前后都不会与其他字符相邻,或者在行首,或者紧跟着一个单词分隔符,例如空格、Tab 等,因此这里可以通过一个正则表达式来获取这样的单词,并且将它与上面列出的 keywords
列表进行匹配,与 keywords
匹配中的,将识别为关键字,而同样格式但不在关键字列表中的标识符,将它识别为变量:
/@?[a-zA-Z][\w$]*/
当然,我们还需要定义字符串和注释是什么样的格式,而这个同样可以使用正则表达式来完成,例如,一个通过双引号包含的字符串通常可以用以下正则表达式来表示:
/".*?"/
以及使用双斜杠表示单行注释可以用以下正则表达式来匹配:
/\/\//
最终,就可以通过 Monaco Editor 的 setMonarchTokensProvider 方法,来将整个语法高亮添加到我们的自定义语言中:
monaco.languages.setMonarchTokensProvider('mylang', {
keywords,
tokenizer: {
root: [
[ /@?[a-zA-Z][\w$]*/, {
cases: {
'@keywords': 'keyword',
'@default': 'variable',
}
}],
[/".*?"/, 'string'],
[/\/\//, 'comment'],
]
}
});
现在来看看我们获得了什么:
铛铛,一个具备了关键字、字符串和注释的语法高亮支持!
这里似乎漏掉了什么?对!这里的颜色都是 Monaco Editor 自带主题对于关键字、字符串和注释颜色的定义,如果我们需要定义不同的颜色该如何去做?
同样的,Monaco Editor 提供了很简便的方法,用来自定义一个主题:
monaco.editor.defineTheme('mylang-theme', {
base: 'vs',
rules: [
{ token: 'keyword', foreground: '#FF6600', fontStyle: 'bold' },
{ token: 'comment', foreground: '#999999' },
{ token: 'string', foreground: '#009966' },
{ token: 'variable', foreground: '#006699' },
],
colors: {}
});
啊哈,看我们得到了一个完全不一样的语法高亮结果:
到这里,我们已经完成了基本了自定义语言的关键字语法高亮功能,这个编辑器在自定义语言这个领域里,已经具备初步可用的能力了。
Step 3. 添加自动完成
在使用一个代码编辑器时,还有一个重要的功能是什么?对,就是自动完成!
很多时候我们并不想去记忆那么多的关键字,只想通过敲击关键字的前几个字符,剩下的请编辑器来帮我自动补全吧。而这个功能,Monaco Editor 同样提供了良好的支持。
通过 Monaco Editor 的注册自动完成提供者功能,就可以很轻松的为我们的自定义语言添加自动完成能力。
在这里,我们将为所有关键字,提供自动补全能力,用户在编辑器中输入关键字列表中所有关键字的前几个字符时,将得到一个自动完成列表来提示完整的关键字是什么,并且可以通过 Tab 键来完成自动输入。
monaco.languages.registerCompletionItemProvider('mylang', {
provideCompletionItems: (model, position) => {
const suggestions = [
...keywords.map(k => {
return {
label: k,
kind: monacoEditor.languages.CompletionItemKind.Keyword,
insertText: k,
};
})
];
return { suggestions: suggestions };
}
});
是不是很简单?来来看效果怎么样:
可以看到,只输入了 st 两个字符,编辑器便为我们提示了完整的关键字为 string,并且可以 Tab 来一键完成输入。
Step 4. 添加错误提示
最后,如果我们的自定义语言,背后也有相应的编译器来支持的,那么,我们需要在用户输入了不符合我们预期的内容时,给于提示,便于用户检测代码中哪里有错误,从而可以快速定位问题并修正问题。
例如在 mylang
中,string 是一个关键字,但是 strig 并不是,如果在代码中输入了 strig,并且我们的编译器可以指出这个错误的话,那么我们就可以通过 Monaco Editor 的 Marker 功能,将这个问题标识出来,从而用户可以很快知道代码错误在哪里,可以更加迅速地去修复这个问题。
例如后端编译器可以给出这样一个错误信息的结构:
let err = {
message: 'unknow type',
line: 4,
column: 5,
length: 5
};
那么,就可以通过 Monaco Editor 去在代码编辑器提示这个错误:
let markers = [];
if (err) {
markers.push({
startLineNumber: err.line,
endLineNumber: err.line,
startColumn: err.column,
endColumn: err.column + err.length,
message: err.message,
severity: monaco.MarkerSeverity.Error,
});
}
monaco.editor.setModelMarkers(editor.getModel(), "owner", markers);
请注意,这里的 editor
为创建 Monaco Editor 的实例对象。
最终,我们得到了一个有用的错误信息提示,用户可以将鼠标放在这个错误上并得到它对应的错误信息:
至此,我们通过 Monaco Editor 提供的各项能力,完成了一个自定义语言 mylang
的完整的开发支持能力,包括了关键字语法高亮、关键字自动完成、编译错误提示这些必备功能。
在最后
当然,不要忘记在初始化 Monaco Editor 实例的时候,将它的语言指定为我们的自定义语言 mylang
,否则它将不会按我们的预期去渲染和提示代码。
const container = document.getElementById('container');
editor = monaco.editor.create(container, {
theme: 'mylang-theme',
language: 'mylang',
fontFamily: 'Menlo',
fontSize: 12,
value: `// A demo class
class ABC {
private:
string field1 = "static string";
string field2;
public:
string method1();
string method2();
}`,
});
另外,Monaco Editor 对于自定义语言的支持不仅限于以上这些功能,还有更多强大的能力可以深入到 Monaco Editor 的 API 中去发掘。
希望此文对你有所帮助。
“monaco-editor”: “^0.44.0”
在自定义主题方面,defineTheme 方法中 ,color 属性务必要填写,否则 error
monaco.editor.defineTheme(‘mylang-theme’, {
base: ‘vs’,
// here
colors: {},
rules: [
{ token: ‘keyword’, foreground: ‘#FF6600’, fontStyle: ‘bold’ },
{ token: ‘comment’, foreground: ‘#999999’ },
{ token: ‘string’, foreground: ‘#009966’ },
{ token: ‘variable’, foreground: ‘#006699’ },
],
});
感谢指正,在抽取这块代码的时候漏掉了。