monaco-editor 介绍

微软之前有个项目叫做 Monaco Workbench,后来这个项目变成了VSCode,而 Monaco Editor 就是从这个项目中成长出来的一个web编辑器,他们很大一部分的代码(monaco-editor-core)都是共用的,所以monaco和VSCode在编辑代码,交互以及UI上几乎是一摸一样的,有点不同的是,两者的平台不一样,monaco基于浏览器,而VSCode基于electron,所以功能上VSCode更加健全,并且性能比较强大。

简单来讲,monaco-editor 是一个浏览器版本的 vscode。目前很多浏览器上的 "云编辑器" 都是基于 monaco-editor 来做的。

安装引入

安装

tnpm install monaco-editor

页面注册使用

import * as monaco from 'monaco-editor';
import React, { useRef, useEffect } from 'react';

const CodeEditor: React.FC = () => {
const editorRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (editorRef.current) {
const editorIns = monaco.editor.create(editorRef.current, {
language: 'sql',
value,
folding: true,
theme: 'vs',
scrollbar: {
verticalScrollbarSize: 8,
horizontalScrollbarSize: 8,
},
minimap: {
enabled: withMiniMap,
},
formatOnPaste: true,
renderValidationDecorations: 'on',
});
}
}, [editorRef]);
return <div ref={editorRef}></div>;
};

monaco-editor-webpack-plugin

在 monaco-editor 中需要处理 ts、html、json等语言时需要单独的引入相应的 worker 。当我们使用 webpack 来作为构建工具的时候会不是很方便,这个时候可以通过 ​​monaco-editor-webpack-plugin​​ 来帮助我们处理这些问题,它可以用来做:

  1. 自动注入getWorkerUrl全局变量;
  2. 处理worker的编译配置;
  3. 自动引入控件和语言包。

相关功能

编辑器适配屏幕缩放

  1. 手动调用​​editorIns.layout()​​​ 方法​​文档​
  2. 实例化编辑器的时候​​automaticLayout​​​ 声明为​​true​​​​文档​​ (可能有性能问题)

Diff Editor

  1. 通过​​monaco.editor.createDiffEditor​​ 方法创建 diff editor 实例;
  2. 通过​​diffEditorIns.setModel​​ 方法设置 diff 的原始值和现在的值
diffEditorIns.setModel({
original: monaco.editor.createModel(originalValue, 'javascript'),
modified: monaco.editor.createModel(nowValue, 'javascript'),
});

更新编辑器 options

通过 ​​updateOptions​​ 来更新,比如

editorIns.updateOptions({
readOnly: true,
});

自定义语言高亮

  1. 如果是自定义的语言需要先注册自定义语言名称
monaco.languages.register({ id: 'configItem' });
  1. 定义高亮规则​​setMonarchTokensProvider 文档​
// 如果是已有语言高亮则不需要第一步直接设置高亮规则就可以
monaco.languages.setMonarchTokensProvider('yaml', {
tokenizer: {
root: [[tokenRegx, { token: 'keyword' }]],
},
});
// 自定义语言
monaco.languages.setMonarchTokensProvider('configItem', {
tokenizer: {
root: [[tokenRegx, { token: 'keyword' }]],
},
});

标记错误

通过 ​​monaco.editor.setModelMarkers​​​ 方法标记位置点

monaco.editor.setModelMarkers(
model,
'javascript',
[{
startLineNumber: 2,
endLineNumber: 2,
startColumn: 1,
endColumn: 10,
severity: monaco.MarkerSeverity.Error,
message: `语法错误`,
}],
);

Quick Fix

通过 ​​setModelMarkers​​​ 标记错误或者警告之后 hover 会有一个 modal 里面展示错误信息以及 quick fix 的选项,如何来定义 quick fix 的行为呢。 这里需要通过 ​​monaco.languages.registerCodeActionProvider​​​ 来定义quick fix 的行为,支持 ​​edit​​​ 和 ​​command​​ 两种类型的行为。

  1. ​edit​​ 是直接替换被指定的位置的文本;
  2. ​command​​ 则是完全的自定义 fix 行为,可以做任何事情。

CompleteProvider

用于定义自动完成的 provider

HoverProvider

用于定义鼠标 hover 的 provider。

public provideHover(
model: monaco.editor.ITextModel,
position: monaco.Position,
token: monaco.CancellationToken,
)

PS: 貌似不支持关键词定义 hover tips 解决方案:

  1. 获取 hover 位置;
  2. 获取 hover 的那一行内容​​model.getLineContent(lineNumber)​​ ;
  3. 匹配 hover 行关键字;
  4. 获取关键字位置;
  5. 根据 鼠标 hover column 确定 hover 的关键字 位置;
  6. 提示 关键字内容等。

本地化 i18n

目前 monaco-editor 官方仅支持 AMD 方式的 i18n 配置。 补丁方案 ​​monaco-editor-esm-webpack-plugin​

Dispose

monaco-editor 中提供了许多的回调以及 provider 供我们使用,一些场景下我们需要 ​​removeEventListener​​​ ,这个时候我们可以使用 ​​dispose​​​ 来实现。大部分情况下各种回调或 provider 都是返回一个包含 ​​dispose​​ 函数的对象,只要执行下这个函数就可以解除功能。

let disposeable: monaco.IDisposable;

// 取消 hover provider
disposeable?.dispose();

disposeable = monaco.languages.registerHoverProvider(language, new ConfigItemHoverProvider(configItemList));

参考

  1. ​monaco-editor​
  2. ​monaco-editor playground​
  3. ​monaco-editor-webpack-plugin​