阅读15分钟
目前我司的多个产品中都支持在线编辑 SQL 来生成对应的任务。为了优化用户体验,在使用
MonacoEditor
为编辑器的基础上,我们还支持了如下几个重要功能:
多种 SQL 的语法高亮
多种 SQL 的报错提示(错误位置飘红)
多种 SQL 的自动补全(智能提示)
本文旨在讲解上述功能的实现思路,对于技术细节,由于篇幅原因不会阐述的太详细。
Monaco Languages
Monaco Editor 内置的 languages
Monaco Editor 内置了相当多的 languages,比如
javaScript
、
CSS
、
Shell
等。
Monaco Editor 依赖包的 ESM 入口文件为
./esm/vs/editor/editor.main.ts
而在入口文件中,Monaco Editor 引入了所有内置的 Languages。
这里 languages 文件可以分为两类,一类是
../language
文件夹下的,支持自动补全和飘红提示等高级功能;另一类则是
../basic-languages
文件夹下的,只支持一些基本功能。
使用内置的 Language 功能
以使用
typescript
language 为例:
import { editor } from 'monaco-editor';
const container = document.getElementById('container');
editor.create(container, {
language: 'typescript'
此时我们会发现,编辑器已经有语法高亮的功能了,但是浏览器控制台会抛异常,另外也没有自动补全功能和飘红提示功能,
这其实是因为,Monaco Editor 无法加载到 language 对应的 worker,对应的解决办法看这里: Monaco integrate-esm。
这里我们使用 Using plain webpack
的方式,首先将对应的 worker 文件设置为 webpack entry:
module.exports = {
entry: {
index: path.resolve( __dirname, './src/index.ts'),
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
'ts.worker': 'monaco-editor/esm/vs/language/typescript/ts.worker.js'
另外还需要设置 Monaco Editor 的全局环境变量,这主要是为了告诉 Monaco Editor 对应的 worker 文件的路径
import { editor } from 'monaco-editor';
(window as any).MonacoEnvironment = {
getWorkerUrl: function (_moduleId, label) {
switch (label) {
case 'flink': {
return './flink.worker.js';
case 'typescript': {
return './ts.worker.js'
default: {
return './editor.worker.js';
const container = document.getElementById('container');
editor.create(container, {
language: 'typescript'
这样一个具有语法高亮
、自动补全
、飘红提示
功能的 typescript 编辑器就设置好了
首先上文中提到了当我们直接从 Monaco Editor 的入口文件中导入时,会自动的引入所有内置的 Languages,但是实际上这其中绝大都是我们不需要的,而由于其导入方式,很显然我们不需要的 languages 也无法被 treeShaking。要解决这个问题我们可以选择从 monaco-editor/esm/vs/editor/editor.api
文件中导入Monaco Editor 核心 API,然后通过 monaco-editor-webpack-plugin 来按需导入所需要的功能。另外这个插件也可以自动处理Monaco Editor 内置的 worker 文件的打包问题,以及自动注入 MonacoEnvironment
全局环境变量。
自定义 Language
注册Language
Monaco Editor 提供了 monaco.languages.register
方法,用来自定义 language
* Register information about a new language.
export function register(language: ILanguageExtensionPoint): void;
export interface ILanguageExtensionPoint {
id: string;
extensions?: string[];
filenames?: string[];
filenamePatterns?: string[];
firstLine?: string;
aliases?: string[];
mimetypes?: string[];
configuration?: Uri;
第一步,我们需要注册一个 language, 配置项中 id 对应的就是语言名称(其他配置项可以暂时不填),这里自定义的 language 名为 myLang
import { editor, languages } from 'monaco-editor';
languages.register({
id: "myLang"
const container = document.getElementById('container');
editor.create(container, {
language: 'myLang'
此时可以发现,页面上的编辑器没有任何其他附加功能,就是普通的文本编辑器。
设置 Language
通过 monaco.languages.setLanguageConfiguration
,可以对 language 进行配置
* Set the editing configuration for a language.
export function setLanguageConfiguration(
languageId: string,
configuration: LanguageConfiguration
): IDisposable;
* The language configuration interface defines the contract between extensions and
* various editor features, like automatic bracket insertion, automatic indentation etc.
export interface LanguageConfiguration {
comments?: CommentRule;
brackets?: CharacterPair[];
wordPattern?: RegExp;
indentationRules?: IndentationRule;
onEnterRules?: OnEnterRule[];
autoClosingPairs?: IAutoClosingPairConditional[];
surroundingPairs?: IAutoClosingPair[];
colorizedBracketPairs?: CharacterPair[];
autoCloseBefore?: string;
folding?: FoldingRules;
这些配置会影响 Monaco Editor 的一些默认行为,比如设置 autoClosingPairs
中有一项为一对圆括号,那么当输入左圆括号后,会自动补全右圆括号。
import { languages } from "monaco-editor";
const conf: languages.LanguageConfiguration = {
comments: {
lineComment: "--",
blockComment: ["/*", "*/"],
brackets: [
["(", ")"],
autoClosingPairs: [
{ open: "(", close: ")" },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
surroundingPairs: [
{ open: "(", close: ")" },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
languages.setLanguageConfiguration('myLang', conf)
Monarch
Moanco Editor 内置了 Monarch,用于实现语法高亮功能,它本质上是一个有限状态机,我们可以通过JSON的形式来配置其状态流转逻辑,并通过monaco.languages.setMonarchTokensProvider
API 应用该配置。关于Monarch 的具体用法可以看一下这篇文章 以及 Monarch Document。
配置中最重要的是 tokenizer
属性,意思是分词器,分词器会自动对编辑器内部的文本进行分词处理,每个分词器都有一个 root state,在 root state 中可以有多条规则,规则内部可以引用其他 state。
下面是一个简单的配置示例
import { languages } from "monaco-editor";
export const language: languages.IMonarchLanguage = {
ignoreCase: true,
tokenizer: {
root: [
{ include: '@comments' },
{ include: '@whitespace' },
{ include: '@strings' },
whitespace: [[/\s+/, 'white']],
comments: [
[/--+.*/, 'comment'],
[
comment: [
[/[^*/]+/, 'comment'],
[