如何使用MonacoEditor写一个可以打断点的java编辑器支持语法高亮等

郦昆
2023-12-01

MonacoEditor

先附上官网地址你们会用到,里面有大量API功能非常丰富,本文中所用到的API里面都会提及。
MonacoEditor.

引入MonacoEditor

这里并不一定这样,引入的方式有很多,也可直接使用monaco-editor引入。

// An highlighted block
import loader from "@monaco-editor/loader";
loader.config({
  paths: {
    vs: "https://g.alicdn.com/code/lib/monaco-editor/0.31.1/min/vs"
  }
});
 return new Promise((resolve, inject) => {
        loader
          .init()
          .then(monaco => {
            // 兼容旧版本 monaco-editor 写死 MonacoEnvironment 的问题
            window.MonacoEnvironment = undefined;
            if (typeof window.define === "function" && window.define.amd) {
              // make monaco-editor's loader work with webpack's umd loader
              // @see https://github.com/microsoft/monaco-editor/issues/2283
              delete window.define.amd;
            }

            resolve({
              code: 1,
              monaco
            });
          })
          .catch(err => {
            inject({
              code: 0,
              err
            });
          });
      });

设置语言类型

我们这里自定义一个customjava的语言,他是要基于java语言的

//monaco 为引入的monaco
this.monaco = monaco;
//在这里我们注册了customjava的预览类型
this.monaco.languages.register({ id: "customjava" });
//customTokenProvider 为customjava预览的配置,包括关键字高亮等信息
//你可以在文章结尾处获得customTokenProvider详细配置
this.monaco.languages.setMonarchTokensProvider("customjava",customTokenProvider);
// 注册主题
//你可以在文章结尾处获得customTheme主题配置,其中自定义了三中类型
this.monaco.editor.defineTheme("customTheme", customTheme);
// 注册语言配置,设置customjava的一些快捷操作
//文章结尾处获取该方法customLanguageConfiguration
this.monaco.languages.setLanguageConfiguration(
  "customjava",
  customLanguageConfiguration(this.monaco)
);
//customjava代码格式化问题,这里使用了groovyBeautify 他是npm里第三方格式化java代码的插件库
 monaco.languages.registerDocumentFormattingEditProvider("customjava", {
   provideDocumentFormattingEdits: function(model, options, token) {
     var formattedCode = groovyBeautify(model.getValue());
     return [
       {
         range: model.getFullModelRange(),
         text: formattedCode
       }
     ];
   }
 });
 //customjava代码提示问题
 //这里使用了registerCompletionItemProvider
 this.languages = monaco.languages.registerCompletionItemProvider(
   "customjava",
   {
     provideCompletionItems: () => {
       let suggestions = [];
         suggestions.push({
           label: 提示信息,
           //插入文本的方式,具体可以console monaco.languages.CompletionItemKind 
           kind: monaco.languages.CompletionItemKind[],
           insertText: 插入的文本,
           documentation: 插入的详情
       });
       return {
         suggestions: suggestions
       };
     }
   }

使用customjava

this.monacoSqlEditor = monaco.editor.create(this.$refs.sqlContainer, {
	  value: "",
	  language:"customjava",
	  contextmenu: true,
	  automaticLayout: true,
	  fontSize: 16,
	  theme: "customTheme",
	  inherit: true,
	  minimap: {
	    enabled: true
	  }
});

断点配置

存储字段

      decorations: [],

对事件的监听

//鼠标移入左侧时开始打断点
		this.monacoSqlEditor.onMouseMove(e => {
          this.removeFakeBreakPoint();
          if (
            e.target.detail &&
            e.target.detail.offsetX &&
            e.target.detail.offsetX >= 0 &&
            e.target.detail.offsetX <= 40
          ) {
            let line = e.target.position.lineNumber;
            let lineContent = this.monacoSqlEditor
              .getModel()
              .getLineContent(line)
              .trim();
            if (lineContent) {
              this.addFakeBreakPoint(line);
            }
          }
        });
        this.monacoSqlEditor.onMouseDown(e => {
          if (
            e.target.detail &&
            e.target.detail.offsetX &&
            e.target.detail.offsetX >= 0 &&
            e.target.detail.offsetX <= 40
          ) {
            let line = e.target.position.lineNumber;
            let lineContent = this.monacoSqlEditor
              .getModel()
              .getLineContent(line)
              .trim();
            if (lineContent) {
              if (!this.hasBreakPoint(line)) {
                this.addBreakPoint(line);
              } else {
                this.removeBreakPoint(line);
              }
              this.getBreakpoint();
            }
          }
        });

断点中使用的方法

初始化获取断点

    getBreakpoint() {
      let breakpointLines = [];
      if (this.monacoSqlEditor) {
        let decorations = this.monacoSqlEditor.getModel().getAllDecorations();
        let ids = [];
        for (let decoration of decorations) {
          if (
            decoration.options.linesDecorationsClassName === "breakpoints-code"
          ) {
            breakpointLines.push(decoration.range.startLineNumber);
          }
        }
      }
    },

清空断点

    removeFakeBreakPoint() {
      let model = this.monacoSqlEditor.getModel();
      this.decorations = model.deltaDecorations(this.decorations, []);
    },

添加断点

 addFakeBreakPoint(line) {
      let model = this.monacoSqlEditor.getModel();
      if (this.hasBreakPoint(line)) return;
      let value = {
        range: new window.monaco.Range(line, 1, line, 1),
        options: {
          isWholeLine: true,
          linesDecorationsClassName: "breakpoints-fake-code"
        }
      };
      this.decorations = model.deltaDecorations(this.decorations, [value]);
    },

检测当前是否存在断点

	hasBreakPoint(line) {
      let decorations = this.monacoSqlEditor.getLineDecorations(line);
      for (let decoration of decorations) {
        if (
          decoration.options.linesDecorationsClassName === "breakpoints-code"
        ) {
          return true;
        }
      }
      return false;
    },

当前出添加断点

    async addBreakPoint(line) {
      let model = this.monacoSqlEditor.getModel();
      if (!model) return;
      let value = [
        {
          range: new window.monaco.Range(line, 1, line, 1),
          options: {
            isWholeLine: true,
            linesDecorationsClassName: "breakpoints-code"
          }
        },
        {
          range: new window.monaco.Range(line, null, line, null),
          options: {
            isWholeLine: true,
            className: "debug-line-color"
          }
        }
      ];
      model.deltaDecorations([], value);
    },

删除当前断点

    async removeBreakPoint(line) {
      let model = this.monacoSqlEditor.getModel();
      if (!model) return;
      let decorations;
      let ids = [];
      if (line !== undefined) {
        decorations = this.monacoSqlEditor.getLineDecorations(line);
      } else {
        decorations = this.monacoSqlEditor.getModel().getAllDecorations();
      }
      for (let decoration of decorations) {
        if (decoration.options.className === "debug-line-color") {
          ids.push(decoration.id);
        }
        if (
          decoration.options.linesDecorationsClassName === "breakpoints-code"
        ) {
          ids.push(decoration.id);
        }
      }
      if (ids && ids.length) {
        model.deltaDecorations(ids, []);
      }
    },

customjava 的语法检查

这里提供一种思路,因为语法检查需要在后台完成,前端可以不断通过socket向后端发送信息,后端校验后返回给前端结果,第几行第几列某个字段报错。

customTokenProvider

const customTokenProvider = {
  defaultToken: "",
  tokenPostfix: ".java",
  keywords: [
    "abstract",
    "assert",
    "goto",
    "package",
    "synchronized",
    "boolean",
    "private",
    "this",
    "double",
    "implements",
    "protected",
    "throw",
    "byte",
    "import",
    "public",
    "throws",
    "enum",
    "instanceof",
    "transient",
    "extends",
    "int",
    "short",
    "char",
    "final",
    "interface",
    "static",
    "void",
    "class",
    "finally",
    "long",
    "strictfp",
    "volatile",
    "const",
    "float",
    "native",
    "super",
    "true",
    "false",
    "def",
    "null"
  ],
  keywordsControl: [
    "try",
    "catch",
    "return",
    "new",
    "break",
    "case",
    "else",
    "if",
    "switch",
    "for",
    "while",
    "do",
    "default",
    "continue"
  ],
  operators: [
    "=",
    ">",
    "<",
    "!",
    "~",
    "?",
    ":",
    "==",
    "<=",
    ">=",
    "!=",
    "&&",
    "||",
    "++",
    "--",
    "+",
    "-",
    "*",
    "/",
    "&",
    "|",
    "^",
    "%",
    "<<",
    ">>",
    ">>>",
    "+=",
    "-=",
    "*=",
    "/=",
    "&=",
    "|=",
    "^=",
    "%=",
    "<<=",
    ">>=",
    ">>>="
  ],
  // typeKeywords: [
  //   "aaa",
  // ],
  // we include these common regular expressions
  symbols: /[=><!~?:&|+\-*\/\^%]+/,
  escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
  digits: /\d+(_+\d+)*/,
  octaldigits: /[0-7]+(_+[0-7]+)*/,
  binarydigits: /[0-1]+(_+[0-1]+)*/,
  hexdigits: /[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,
  // keywordsss: ["error", "info", "warning"],
  // The main tokenizer for our languages
  tokenizer: {
    root: [
      // Special keyword with a dash
      [
        /[A-Z][a-z|0-9|\u4e00-\u9fa5|@|_|-]*/,
        {
          token: "custom-variable"
        }
      ],
      [
        /[a-zA-Z_$][\w$]*/,
        {
          cases: {
            "@keywords": { token: "keyword.$0" },
            "@keywordsControl": { token: "custom-keywords" },
            "@default": "identifier"
          }
        }
      ],
      { include: "@whitespace" },

      // delimiters and operators
      [/[{}()\[\]]/, "@brackets"],
      [/[<>](?!@symbols)/, "@brackets"],
      [
        /@symbols/,
        {
          cases: {
            "@operators": "delimiter",
            "@default": ""
          }
        }
      ],

      // @ annotations.
      [/@\s*[a-zA-Z_\$][\w\$]*/, "annotation"],

      // numbers
      [/(@digits)[eE]([\-+]?(@digits))?[fFdD]?/, "number.float"],
      [/(@digits)\.(@digits)([eE][\-+]?(@digits))?[fFdD]?/, "number.float"],
      [/0[xX](@hexdigits)[Ll]?/, "number.hex"],
      [/0(@octaldigits)[Ll]?/, "number.octal"],
      [/0[bB](@binarydigits)[Ll]?/, "number.binary"],
      [/(@digits)[fFdD]/, "number.float"],
      [/(@digits)[lL]?/, "number"],

      // delimiter: after number because of .\d floats
      [/[;,.]/, "delimiter"],

      // strings
      [/"([^"\\]|\\.)*$/, "string.invalid"], // non-teminated string
      [/"/, "string", "@string"],

      // characters
      [/'[^\\']'/, "string"],
      [/(')(@escapes)(')/, ["string", "string.escape", "string"]],
      [/'/, "string.invalid"]
    ],

    // text: [
    //   [/^@header/, { token: "@rematch", next: "@pop" }],
    //   [/.*/, { token: "custom-$S2" }]
    // ],
    whitespace: [
      [/[ \t\r\n]+/, ""],
      [/\/\*\*(?!\/)/, "comment.doc", "@javadoc"],
      [/\/\*/, "comment", "@comment"],
      // [/\/\/.*$/, "custom-note"]
      [/\/\/.*$/, "comment"]
    ],

    comment: [
      [/[^\/*]+/, "comment"],
      // [/\/\*/, 'comment', '@push' ],    // nested comment not allowed :-(
      // [/\/\*/,    'comment.invalid' ],    // this breaks block comments in the shape of /* //*/
      [/\*\//, "comment", "@pop"],
      [/[\/*]/, "comment"]
    ],
    //Identical copy of comment above, except for the addition of .doc
    javadoc: [
      [/[^\/*]+/, "comment.doc"],
      // [/\/\*/, 'comment.doc', '@push' ],    // nested comment not allowed :-(
      [/\/\*/, "comment.doc.invalid"],
      [/\*\//, "comment.doc", "@pop"],
      [/[\/*]/, "comment.doc"]
    ],

    string: [
      [/[^\\"]+/, "string"],
      [/@escapes/, "string.escape"],
      [/\\./, "string.escape.invalid"],
      [/"/, "string", "@pop"]
    ]
    // javaVariabel:[
    //   [/def+/, "variabel"],
    // ]
  }
}

customTheme

const customTheme = {
  base: "vs-dark", // 要继承的基础主题,即内置的三个:vs、vs-dark、hc-black
  inherit: true, // 是否继承
  rules: [
    { token: "custom-variable", foreground: "4ec9b0" },
    { token: "custom-keywords", foreground: "c586c0" },
    { token: "custom-note", foreground: "6a9955" }
  ],
  colors: {
    "editor.background": '#151414'
  }
}

customLanguageConfiguration

const customLanguageConfiguration = monaco => {
  return {
    wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
    comments: {
      lineComment: "//",
      blockComment: ["/*", "*/"]
    },
    brackets: [
      ["{", "}"],
      ["[", "]"],
      ["(", ")"]
    ],
    onEnterRules: [
      {
        // e.g. /** | */
        beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
        afterText: /^\s*\*\/$/,
        action: {
          indentAction: monaco.languages.IndentAction.IndentOutdent,
          appendText: " * "
        }
      },
      {
        // e.g. /** ...|
        beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
        action: {
          indentAction: monaco.languages.IndentAction.None,
          appendText: " * "
        }
      },
      {
        // e.g.  * ...|
        beforeText: /^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/,
        action: {
          indentAction: monaco.languages.IndentAction.None,
          appendText: "* "
        }
      },
      {
        // e.g.  */|
        beforeText: /^(\t|(\ \ ))*\ \*\/\s*$/,
        action: {
          indentAction: monaco.languages.IndentAction.None,
          removeText: 1
        }
      }
    ],
    autoClosingPairs: [
      { open: "{", close: "}" },
      { open: "[", close: "]" },
      { open: "(", close: ")" },
      { open: '"', close: '"' },
      { open: "'", close: "'" },
      { open: "<", close: ">" },
      { open: "`", close: "`", notIn: ["string", "comment"] },
      { open: "/**", close: " */", notIn: ["string"] }
    ],
    surroundingPairs: [
      { open: "{", close: "}" },
      { open: "[", close: "]" },
      { open: "(", close: ")" },
      { open: '"', close: '"' },
      { open: "'", close: "'" },
      { open: "<", close: ">" }
    ],
    folding: {
      markers: {
        start: new RegExp("^\\s*//\\s*(?:(?:#?region\\b)|(?:<editor-fold\\b))"),
        end: new RegExp("^\\s*//\\s*(?:(?:#?endregion\\b)|(?:</editor-fold>))")
      }
    }
  };
}
 类似资料: