monaco-editor 使用总结

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' });
    
  2. 定义高亮规则 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 的行为,支持 editcommand 两种类型的行为。

  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