Monaco Editor的使用

3,721 阅读3分钟

概述

项目当中,难免会遇到代码编辑器的功能需求,自己实现肯定不现实的,因此开源库成为了首要选择,因此这里主要介绍Monaco Editor的使用,Monaco Editor 是一个浏览器端的代码编辑器库,同时它也是 VS Code 所使用的编辑器,因此该插件功能十分强大,但是官方的文档看着实在是不太友好,因此这里简单记录插件的基本使用

具体使用

安装

npm install monaco-editor

创建编辑器

import * as monaco from "monaco-editor";
import "./work.js";
window.onload = () => {
  const container = document.querySelector(".box");
  const value = `function hello() {
    alert('Hello world!');
  }`;

  const options = {
    value, //值
    language: "javascript", //语言
    theme: "vs-dark", //主题
      contextmenu: false, //禁用右击菜单
    readOnly: true, //是否只读
    automaticLayout: true, // 自动布局
    minimap: {
      // 关闭小地图
      enabled: true,
    },
  };
  const editor = monaco.editor.create(container, options);
};

设置代码提示

一定要导入对应的代码提示文件,不然页面代码输入没有代码提示 ./work.js

import * as monaco from "monaco-editor";
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";

self.MonacoEnvironment = {
  getWorker(_, label) {
    if (label === "json") {
      return new jsonWorker();
    }
    if (label === "css" || label === "scss" || label === "less") {
      return new cssWorker();
    }
    if (label === "html" || label === "handlebars" || label === "razor") {
      return new htmlWorker();
    }
    if (label === "typescript" || label === "javascript") {
      return new tsWorker();
    }
    return new editorWorker();
  },
};

monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);

自定义联想代码补全

monaco.languages.registerCompletionItemProvider("sql", {
  // 触发条件,也可以不写,不写的话只要输入满足配置的label就会提示;仅支持单字符(重要)
  triggerCharacters: [".", " ",'s'],
  provideCompletionItems(model, position) {
    console.log("model, position", model, position);
    return {
      suggestions: [
        {
          label: "test",//输入对应文本后出现提示
          insertText: "测试文件",//回车插入的文本
          kind: monaco.languages.CompletionItemKind.Color,//前面的图标
          detail: "m描述",//后面的描述
        },
        {
          label: "test2",
          insertText: "测试文件2",
          kind: monaco.languages.CompletionItemKind.Constant,
          detail: "m描述2",
        },
        {
          label: "test3",
          insertText: "测试文件3",
          kind: monaco.languages.CompletionItemKind.Method,
          detail: "m描述3",
        },
      ],
    };
  },
});

自定义格式化

import { format } from "sql-formatter";

// 格式化sql
monaco.languages.registerDocumentFormattingEditProvider("sql", {
  displayName: "格式化sql",
  provideDocumentFormattingEdits(model) {
    return [
      {
        text: format(myEditor.getValue()), //格式化文本
        range: model.getFullModelRange(),//范围为全文档
      },
    ];
  },
});

自定义右击菜单

//实例方法使用
editorInstance.addAction({
  id: "custom.action",
  label: "格式化文本",
  keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_J], // 可选的快捷键
  contextMenuGroupId: "9_cutcopypaste", // 上下文菜单组见输出菜单逻辑中的“contextMenuLinks”的group的值
  run: (editor) => {
    myEditor.setValue(format(myEditor.getValue()));
  },
});

获取到所有的注册的行为(需要id的时候可以查看)

//获取到所有的注册的行为
const allActions = myEditor._actions;

删除默认右击菜单某一项

import * as actions from "monaco-editor/esm/vs/platform/actions/common/actions";

// 移除多余菜单项
// // 要移除的菜单项的命令ID列表
export const removableIds = [
    // 'editor.action.clipboardCutAction',//剪切
    // 'editor.action.clipboardCopyAction',//复制
    // 'editor.action.clipboardPasteAction',//粘贴
  
    'editor.action.refactor',
    'editor.action.sourceAction',
    'editor.action.revealDefinition',
    'editor.action.revealDeclaration',
    'editor.action.goToTypeDefinition',
    'editor.action.goToImplementation',
    'editor.action.goToReferences',
    'editor.action.formatDocument',
    'editor.action.formatSelection',
    'editor.action.changeAll',
    'editor.action.rename',
    'editor.action.quickOutline',
    'editor.action.quickCommand',
    'Peek'
  ];
  //获取 Monaco Editor 的菜单项注册表
const menus = actions.MenuRegistry._menuItems;
//查找编辑器上下文菜单的注册条目
const contextMenuEntry = [...menus].find(
  (entry) =>
    entry[0]._debugName == "EditorContext" || entry[0].id == "EditorContext"
);
//// 从上下文菜单的注册条目中提取菜单项
const contextMenuLinks = contextMenuEntry[1];
console.log(contextMenuLinks);
// 移除菜单项的函数
const removeById = (list, ids) => {
  let node = list._first;
  //// 检查当前菜单项是否应该移除
  do {
    // 注意 node.element?.command?.id || node.element?.title 的用法
    const shouldRemove = ids.includes(
      node.element?.command?.id || node.element?.title
    );
    // 如果应该移除,则从链表中移除当前节点
    if (shouldRemove) {
      list._remove(node);
    }
  } while ((node = node.next));
};
// 通过调用 removeById 函数移除特定的菜单项
removeById(contextMenuLinks, removableIds);

监听文本变化

// 监听编辑器内容变化
this.editor.onDidChangeModelContent(() => { 
// 触发父组件更新代码内容 
this.$emit('update:code', this.editor.getValue()); }
);
//监听是否选中
this.editor.onDidChangeCursorSelection(() => { 
// 触发父组件更新代码内容 
console.log("选中了")
);

常用方法

editor.dispose();//销毁
editor.setValue('')//设置值
editor.getValue();//获取值
editor.getModel().getValueInRange(editor.getSelection());//获取选中内容
//根据范围获取编辑器中的文本(获取固定行,格式化选中文本的时候有用)
myEditor.getModel().getValueInRange({
  startLineNumber: 1,//开始行
  startColumn: 0,//开始列
  endLineNumber: 3,//结束行
  endColumn: 43,//结束列
})

汉化

vue.config.js

    const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const MonacoLocalesPlugin = require('monaco-editor-locales-plugin');


  configureWebpack: (config) => {
    config.plugins.push(
      new MonacoWebpackPlugin({
        languages: ['sql'] // 目前只处理SQL语言
        // features: ['coreCommands', 'find'] // 基本命令和搜索功能
      })
    );
    config.plugins.push(
      new MonacoLocalesPlugin({
        //设置支持的语言
        languages: ['zh-cn'],
        //默认语言
        defaultLanguage: 'zh-cn',
        //打印不匹配的文本
        logUnmatched: false
      })
    );

版本相关

    "monaco-editor": "^0.46.0",
    "monaco-editor-webpack-plugin": "^6.0.0",
    "monaco-editor-locales-plugin": "^0.0.3",

效果

image.png

总结

更多的用法可以参考Monaco Editor在git上面的示例.